Project: * Clean-up (Eclipse cruft, unused files, etc...) * Git-specific changes * Maven POMs clean-up and changes for the build system * Version set to 1.0.0-0 in the development branches * Maven plug-ins updated to latest versions * Very partial dev. documentation added

This commit is contained in:
Emmanuel BENOîT 2011-12-09 08:07:33 +01:00
parent c74e30d5ba
commit 0665a760de
1439 changed files with 1020 additions and 1649 deletions

View file

@ -0,0 +1,80 @@
package com.deepclone.lw.beans.eventlog;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.interfaces.admin.AdminDAO;
import com.deepclone.lw.interfaces.eventlog.LogReader;
import com.deepclone.lw.interfaces.mailer.Mailer;
import com.deepclone.lw.interfaces.sys.Ticker;
import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
public class AdminErrorMailBean
implements InitializingBean , DisposableBean
{
private Ticker ticker;
private LogReader logReader;
private AdminDAO adminDao;
private TransactionTemplate tTemplate;
private Mailer mailer;
private AdminErrorMailTask task;
@Autowired( required = true )
public void setTicker( Ticker ticker )
{
this.ticker = ticker;
}
@Autowired( required = true )
public void setLogReader( LogReader logReader )
{
this.logReader = logReader;
}
@Autowired( required = true )
public void setAdminDao( AdminDAO adminDao )
{
this.adminDao = adminDao;
}
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager tManager )
{
this.tTemplate = new TransactionTemplate( tManager );
}
@Autowired( required = true )
public void setMailer( Mailer mailer )
{
this.mailer = mailer;
}
@Override
public void afterPropertiesSet( )
{
this.task = new AdminErrorMailTask( this.adminDao , this.logReader , this.mailer , this.tTemplate );
this.ticker.registerTask( Frequency.LOW , "Admin error mail sender" , this.task );
}
@Override
public void destroy( )
{
this.task = null;
}
}

View file

@ -0,0 +1,135 @@
package com.deepclone.lw.beans.eventlog;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.cmd.admin.adata.Privileges;
import com.deepclone.lw.cmd.admin.logs.ExceptionEntry;
import com.deepclone.lw.cmd.admin.logs.TraceEntry;
import com.deepclone.lw.interfaces.admin.AdminDAO;
import com.deepclone.lw.interfaces.eventlog.ExtendedLogEntry;
import com.deepclone.lw.interfaces.eventlog.LogReader;
import com.deepclone.lw.interfaces.mailer.MailData;
import com.deepclone.lw.interfaces.mailer.Mailer;
import com.deepclone.lw.sqld.admin.AdminRecord;
class AdminErrorMailTask
implements Runnable
{
private static class ErrorData
{
public final List< AdminRecord > administrators;
public final List< ExtendedLogEntry > logEntries;
public ErrorData( List< AdminRecord > administrators , List< ExtendedLogEntry > logEntries )
{
this.administrators = administrators;
this.logEntries = logEntries;
}
}
private final TransactionTemplate tTemplate;
private final AdminDAO adminDao;
private final LogReader logReader;
private final Mailer mailer;
public AdminErrorMailTask( AdminDAO adminDao , LogReader logReader , Mailer mailer , TransactionTemplate tTemplate )
{
this.adminDao = adminDao;
this.logReader = logReader;
this.mailer = mailer;
this.tTemplate = tTemplate;
}
@Override
public void run( )
{
// Get errors and administrators
ErrorData errorData;
errorData = this.tTemplate.execute( new TransactionCallback< ErrorData >( ) {
@Override
public ErrorData doInTransaction( TransactionStatus status )
{
return getErrorData( );
}
} );
// No receiving administrators or no errors -> exit
if ( errorData.administrators.isEmpty( ) || errorData.logEntries.isEmpty( ) ) {
return;
}
// Generate message contents
String message = this.buildMessage( errorData.logEntries );
for ( AdminRecord admin : errorData.administrators ) {
try {
MailData mail = this.mailer.createMail( "en" , "adminErrorMail" , admin.getAddress( ) );
mail.setData( "contents" , message );
mail.queue( );
} catch ( Exception e ) {
throw new RuntimeException( e );
}
}
}
private ErrorData getErrorData( )
{
List< AdminRecord > admins = new LinkedList< AdminRecord >( );
for ( AdminRecord admin : this.adminDao.listAdministrators( ) ) {
if ( Privileges.BUGM.hasPrivilege( admin.getPrivileges( ) ) ) {
admins.add( admin );
}
}
return new ErrorData( admins , this.logReader.getErrorEntries( ) );
}
private String buildMessage( List< ExtendedLogEntry > logEntries )
{
StringBuilder builder = new StringBuilder( );
SimpleDateFormat dFmt = new SimpleDateFormat( "yyyy-MM-dd" );
SimpleDateFormat tFmt = new SimpleDateFormat( "HH:mm:ss" );
for ( ExtendedLogEntry entry : logEntries ) {
builder.append( "Date: " ).append( dFmt.format( entry.logEntry.getTimestamp( ) ) );
builder.append( "\nTime: " ).append( tFmt.format( entry.logEntry.getTimestamp( ) ) );
builder.append( "\nComponent: " ).append( entry.logEntry.getAbout( ) );
builder.append( "\nMessage: " ).append( entry.logEntry.getEntry( ) ).append( "\n" );
for ( ExceptionEntry eEntry : entry.exceptions ) {
builder.append( "\n " ).append( eEntry.getClassName( ) );
if ( eEntry.getMessage( ) != null && !"".equals( eEntry ) ) {
builder.append( ": " ).append( eEntry.getMessage( ) );
}
int i = 0;
for ( TraceEntry tEntry : eEntry.getTrace( ) ) {
builder.append( "\n " );
if ( i++ > 5 ) {
builder.append( "..." );
break;
}
builder.append( tEntry.getLocation( ) );
}
}
builder.append( "\n\n\n" );
}
return builder.toString( );
}
}

View file

@ -0,0 +1,34 @@
package com.deepclone.lw.beans.eventlog;
import com.deepclone.lw.sqld.sys.SystemLogEntry;
/**
* Log writer queue entries only carry a log entry, which may be null to indicate that the log
* writer's task must be terminated.
*
* @author tseeker
*/
class EntryQueueItem
{
/** The log entry */
final SystemLogEntry entry;
/** Initialises a "termination" queue entry */
EntryQueueItem( )
{
this.entry = null;
}
/** Initialises a log-carrying queue entry */
EntryQueueItem( SystemLogEntry entry )
{
this.entry = entry;
}
}

View file

@ -0,0 +1,107 @@
package com.deepclone.lw.beans.eventlog;
import javax.sql.DataSource;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import com.deepclone.lw.interfaces.sys.Ticker;
import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
/**
* Log clean-up bean.
*
* <p>
* This bean is responsible for removing old log entries from the various logging tables. It
* registers a set of constants which determine the maximal age of a log entry depending on its
* category (administrative, account or system) and starts a ticker task with a high frequency.
*
* @author tseeker
*
*/
public class LogCleanerBean
implements InitializingBean , DisposableBean
{
/** Ticker bean */
private Ticker ticker;
/** Transaction manager */
private PlatformTransactionManager transactionManager;
/** Log cleaner task instance */
private LogCleanerTask cleanerTask;
private DataSource dataSource;
/**
* Sets the ticker bean reference (DI).
*
* @param ticker
* the ticker bean
*/
@Autowired( required = true )
public void setTicker( Ticker ticker )
{
this.ticker = ticker;
}
/**
* Sets the transaction manager reference (DI).
*
* @param manager
* the transaction manager
*/
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager manager )
{
this.transactionManager = manager;
}
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dataSource = dataSource;
}
/**
* Registers constants and initialises the log clean-up task.
*
* <p>
* Once the dependencies have been injected, this method will register the
* <em>log.maxAge.admin</em>, <em>log.maxAge.account</em> and <em>log.maxAge.system</em>
* constants, setting their default values to 7 days. It will then create the clean-up task
* instance, register it as a constants user, and add it to the ticker with a high frequency.
*/
@Override
public void afterPropertiesSet( )
{
// Create clean-up task
this.cleanerTask = new LogCleanerTask( this.dataSource , this.transactionManager );
this.ticker.registerTask( Frequency.LOW , "Log cleaner" , this.cleanerTask );
}
/**
* Removes references to the clean-up task.
*
* <p>
* Before the bean is destroyed, this method will unregister the clean-up task from the
* constants manager and remove the local reference.
*/
@Override
public void destroy( )
{
this.cleanerTask = null;
}
}

View file

@ -0,0 +1,61 @@
package com.deepclone.lw.beans.eventlog;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* Log clean-up task.
*
* <p>
* This class implements the log clean-up task; it is simultaneously a constants user (as it needs
* to keep informed about <em>log.maxAge.*</em>), a runnable (as it needs to be registered as a
* ticker task) and a transaction callback (to perform the actual clean-up).
*
* @author tseeker
*/
class LogCleanerTask
implements Runnable
{
/** Transaction manager interface */
private final TransactionTemplate tTemplate;
private JdbcTemplate dTemplate;
/**
* @param sessionFactory
* the Hibernate session factory
* @param transactionManager
* the transaction manager
*/
LogCleanerTask( DataSource dataSource , PlatformTransactionManager transactionManager )
{
this.tTemplate = new TransactionTemplate( transactionManager );
this.dTemplate = new JdbcTemplate( dataSource );
}
/**
* Executes the clean-up transaction if the constants have been set.
*/
@Override
synchronized public void run( )
{
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
dTemplate.execute( "SELECT sys.clean_logs( )" );
}
} );
}
}

View file

@ -0,0 +1,284 @@
package com.deepclone.lw.beans.eventlog;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.cmd.admin.adata.Administrator;
import com.deepclone.lw.cmd.admin.logs.ExceptionEntry;
import com.deepclone.lw.cmd.admin.logs.GetEntryResponse;
import com.deepclone.lw.cmd.admin.logs.LogEntry;
import com.deepclone.lw.cmd.admin.logs.LogLevel;
import com.deepclone.lw.cmd.admin.logs.LogType;
import com.deepclone.lw.cmd.admin.logs.TraceEntry;
import com.deepclone.lw.cmd.admin.logs.ViewLogResponse;
import com.deepclone.lw.interfaces.eventlog.ExtendedLogEntry;
import com.deepclone.lw.interfaces.eventlog.LogReader;
import com.deepclone.lw.sqld.sys.ExceptionLog;
import com.deepclone.lw.sqld.sys.StackTraceLog;
public class LogReaderBean
implements LogReader
{
private SimpleJdbcTemplate dTemplate;
private TransactionTemplate tTemplate;
private final RowMapper< LogEntry > mLogEntry;
private final RowMapper< ExceptionLog > mExceptionLog;
private final RowMapper< StackTraceLog > mTraceLog;
public LogReaderBean( )
{
this.mLogEntry = new RowMapper< LogEntry >( ) {
@Override
public LogEntry mapRow( ResultSet rs , int rowNum )
throws SQLException
{
LogEntry entry = new LogEntry( );
entry.setId( (Long) rs.getObject( "id" ) );
entry.setTimestamp( rs.getTimestamp( "t" ) );
entry.setAbout( rs.getString( "component" ) );
entry.setLevel( LogLevel.valueOf( rs.getString( "level" ) ) );
entry.setEntry( rs.getString( "message" ) );
entry.setException( (Long) rs.getObject( "exception_id" ) );
return entry;
}
};
this.mExceptionLog = new RowMapper< ExceptionLog >( ) {
@Override
public ExceptionLog mapRow( ResultSet rs , int rowNum )
throws SQLException
{
return new ExceptionLog( rs.getLong( "id" ) , rs.getInt( "depth" ) , rs.getString( "exc_class" ) , rs
.getString( "message" ) );
}
};
this.mTraceLog = new RowMapper< StackTraceLog >( ) {
@Override
public StackTraceLog mapRow( ResultSet rs , int rowNum )
throws SQLException
{
return new StackTraceLog( rs.getLong( "exception_id" ) , rs.getInt( "depth" ) , rs
.getString( "location" ) , rs.getString( "file_name" ) , (Integer) rs.getObject( "line_number" ) );
}
};
}
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dTemplate = new SimpleJdbcTemplate( dataSource );
}
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager tManager )
{
this.tTemplate = new TransactionTemplate( tManager );
}
@Override
public ViewLogResponse getLog( final Administrator admin , final LogType type , final long first , final int count ,
final LogLevel minLevel , final String component , final boolean excOnly )
{
if ( excOnly && type != LogType.SYSTEM ) {
return new ViewLogResponse( admin , 0 , new LinkedList< LogEntry >( ) );
}
return this.tTemplate.execute( new TransactionCallback< ViewLogResponse >( ) {
@Override
public ViewLogResponse doInTransaction( TransactionStatus status )
{
return getLogQueries( admin , type , first , count , minLevel , component , excOnly );
}
} );
}
private ViewLogResponse getLogQueries( Administrator admin , LogType type , long first , int count ,
LogLevel minLevel , String component , boolean excOnly )
{
String countQuery = this.buildQuery( true , type , first , count , minLevel , component , excOnly );
Object[] cqParams = this.buildQueryParams( true , first , count , minLevel , component );
long nEntries = this.dTemplate.queryForLong( countQuery , cqParams );
String mainQuery = this.buildQuery( false , type , first , count , minLevel , component , excOnly );
Object[] mqParams = this.buildQueryParams( false , first , count , minLevel , component );
List< LogEntry > entries = this.dTemplate.query( mainQuery , this.mLogEntry , mqParams );
return new ViewLogResponse( admin , nEntries , entries );
}
private Object[] buildQueryParams( boolean isCount , long first , int count , LogLevel minLevel , String component )
{
boolean hasComponent = ( component != null && !"".equals( component ) );
Object[] params = new Object[ ( hasComponent ? 2 : 1 ) + ( isCount ? 0 : 2 ) ];
int i = 0;
params[ i++ ] = minLevel.toString( );
if ( hasComponent ) {
params[ i++ ] = "%" + component + "%";
}
if ( !isCount ) {
params[ i++ ] = (Long) first;
params[ i++ ] = (Integer) count;
}
return params;
}
private String buildQuery( boolean isCount , LogType type , long first , int count , LogLevel minLevel ,
String component , boolean excOnly )
{
StringBuilder builder = new StringBuilder( );
// Selection from the right view
builder.append( "SELECT " );
if ( isCount ) {
builder.append( "count(*)" );
} else {
builder.append( "*" );
}
builder.append( " FROM sys." ).append( type.toString( ).toLowerCase( ) ).append( "_logs_view WHERE " );
// Minimal log level
builder.append( "level >= ?::log_level" );
// Component
if ( component != null && !"".equals( component ) ) {
builder.append( " AND lower( component ) LIKE ?" );
}
// Exception only
if ( excOnly ) {
builder.append( " AND exception_id IS NOT NULL" );
}
if ( !isCount ) {
// Ordering, index, count
builder.append( " ORDER BY t DESC OFFSET ? LIMIT ?" );
}
return builder.toString( );
}
@Override
public GetEntryResponse getEntry( final Administrator admin , final long id )
{
return this.tTemplate.execute( new TransactionCallback< GetEntryResponse >( ) {
@Override
public GetEntryResponse doInTransaction( TransactionStatus status )
{
return doGetEntryResponse( admin , id );
}
} );
}
private GetEntryResponse doGetEntryResponse( Administrator admin , long id )
{
ExtendedLogEntry lEntry = this.doGetEntry( id );
if ( lEntry == null ) {
return new GetEntryResponse( admin , false );
}
return new GetEntryResponse( admin , lEntry.logEntry , lEntry.exceptions );
}
private ExtendedLogEntry doGetEntry( long id )
{
String sql = "SELECT * FROM sys.system_logs_view WHERE id = ?";
// Get log entry
LogEntry entry;
try {
entry = this.dTemplate.queryForObject( sql , this.mLogEntry , id );
} catch ( EmptyResultDataAccessException e ) {
return null;
}
// Get exceptions
List< ExceptionLog > eLog;
sql = "SELECT * FROM sys.exceptions WHERE log_id = ? ORDER BY depth";
eLog = this.dTemplate.query( sql , this.mExceptionLog , id );
Map< Long , ExceptionLog > eLogId = new HashMap< Long , ExceptionLog >( );
for ( ExceptionLog ee : eLog ) {
eLogId.put( ee.getId( ) , ee );
}
// Get stack trace entries
sql = "SELECT st.* FROM sys.stack_traces st INNER JOIN sys.exceptions e ON e.id = st.exception_id WHERE e.log_id = ? ORDER BY st.depth DESC";
for ( StackTraceLog stLog : this.dTemplate.query( sql , this.mTraceLog , id ) ) {
ExceptionLog ee = eLogId.get( stLog.getExcId( ) );
ee.addStackTrace( stLog );
}
// Generate output
List< ExceptionEntry > exceptions = new LinkedList< ExceptionEntry >( );
for ( ExceptionLog ee : eLog ) {
ExceptionEntry eEntry = new ExceptionEntry( ee.getClassName( ) , ee.getMessage( ) );
List< TraceEntry > stackTrace = new LinkedList< TraceEntry >( );
for ( StackTraceLog stLog : ee.getStack( ) ) {
stackTrace.add( new TraceEntry( stLog.getLocation( ) , stLog.getFileName( ) , stLog.getLine( ) ) );
}
eEntry.setTrace( stackTrace );
exceptions.add( eEntry );
}
return new ExtendedLogEntry( entry , exceptions );
}
@Override
public List< ExtendedLogEntry > getErrorEntries( )
{
List< ExtendedLogEntry > result = new LinkedList< ExtendedLogEntry >( );
String sql = "SELECT * FROM admin.get_error_entries( )";
RowMapper< Long > longLister = new RowMapper< Long >( ) {
@Override
public Long mapRow( ResultSet rs , int rowNum )
throws SQLException
{
return rs.getLong( 1 );
}
};
for ( Long id : this.dTemplate.query( sql , longLister ) ) {
result.add( this.doGetEntry( id ) );
}
return result;
}
@Override
public List< LogEntry > getAdminLogSince( Timestamp timestamp )
{
String sql = "SELECT * FROM sys.admin_logs_view WHERE t >= ? AND level >= 'INFO'::log_level ORDER BY t DESC";
return this.dTemplate.query( sql , this.mLogEntry , timestamp );
}
}

View file

@ -0,0 +1,122 @@
package com.deepclone.lw.beans.eventlog;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import javax.sql.DataSource;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.interfaces.eventlog.LogWriter;
import com.deepclone.lw.sqld.sys.SystemLogEntry;
/**
* The log writer bean, once initialised, spawns a task while continuously examines a blocking
* queue. When entries are added to the queue, they are flushed to the database. When the bean
* container destroys the bean, a queue entry carrying the null value is added to the queue, causing
* the task to terminate.
*
* @author tseeker
*
*/
public class LogWriterBean
implements LogWriter , InitializingBean , DisposableBean
{
/** Transaction management instance */
private TransactionTemplate tTemplate;
/** JDBC data source */
private DataSource dataSource;
/** The queue used for log entries */
private LinkedBlockingQueue< EntryQueueItem > queue = null;
/**
* Sets the JDBC data source (DI)
*
* @param dataSource
* the JDBC data source
*/
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dataSource = dataSource;
}
/**
* Initialises the interface to the transaction manager (DI)
*
* @param manager
* the transaction manager to use
*/
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager manager )
{
this.tTemplate = new TransactionTemplate( manager );
}
/* Documentation in LogWriter interface */
@Override
synchronized public void addEntries( List< SystemLogEntry > entries )
{
if ( this.queue == null ) {
return;
}
LinkedList< EntryQueueItem > items = new LinkedList< EntryQueueItem >( );
for ( SystemLogEntry e : entries ) {
if ( e == null ) {
continue;
}
items.add( new EntryQueueItem( e ) );
}
this.queue.addAll( items );
}
/**
* Bean initialisation - creates the queue and spawns the log writer task.
*/
@Override
public void afterPropertiesSet( )
{
this.queue = new LinkedBlockingQueue< EntryQueueItem >( );
LogWriterTask task;
task = new LogWriterTask( this.queue , this.dataSource , this.tTemplate );
Thread t;
t = new Thread( task );
t.start( );
}
/**
* Bean destruction - inserts a null entry on the log writer queue, then waits for the task to
* terminate.
*/
@Override
public void destroy( )
{
this.queue.add( new EntryQueueItem( ) );
while ( !this.queue.isEmpty( ) ) {
Thread.yield( );
}
synchronized ( this ) {
this.queue = null;
}
}
}

View file

@ -0,0 +1,166 @@
package com.deepclone.lw.beans.eventlog;
import java.sql.Types;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.sqld.sys.ExceptionLog;
import com.deepclone.lw.sqld.sys.StackTraceLog;
import com.deepclone.lw.sqld.sys.SystemLogEntry;
import com.deepclone.lw.utils.StoredProc;
/**
* The log writer task is spawned by the log writer bean. Whenever log entries are pushed to the log
* writer, they are added to a queue which this tasks continuously reads. If a queue item carrying
* the null value is found, the queue is flushed and the task terminates.
*
* @author tseeker
*/
class LogWriterTask
implements Runnable
{
/** The queue */
private final LinkedBlockingQueue< EntryQueueItem > queue;
/** Transaction management interface */
private final TransactionTemplate tTemplate;
private StoredProc fLog;
private StoredProc fException;
private StoredProc fTrace;
/** Copies the required references */
LogWriterTask( LinkedBlockingQueue< EntryQueueItem > queue , DataSource dataSource , TransactionTemplate tTemplate )
{
this.queue = queue;
this.tTemplate = tTemplate;
this.fLog = new StoredProc( dataSource , "sys" , "write_log" );
this.fLog.addParameter( "component" , Types.VARCHAR );
this.fLog.addParameter( "level" , "log_level" );
this.fLog.addParameter( "message" , Types.VARCHAR );
this.fLog.addOutput( "entry_id" , Types.BIGINT );
this.fException = new StoredProc( dataSource , "sys" , "append_exception" );
this.fException.addParameter( "log_id" , Types.BIGINT );
this.fException.addParameter( "class_name" , Types.VARCHAR );
this.fException.addParameter( "message" , Types.VARCHAR );
this.fException.addOutput( "entry_id" , Types.BIGINT );
this.fTrace = new StoredProc( dataSource , "sys" , "append_trace" );
this.fTrace.addParameter( "exc_id" , Types.BIGINT );
this.fTrace.addParameter( "location" , Types.VARCHAR );
this.fTrace.addParameter( "file_name" , Types.VARCHAR );
this.fTrace.addParameter( "line_number" , Types.INTEGER );
}
/**
* Implements the consumer side of the queue; when an item is found, all items are flushed and
* written to the DB in the same transaction.
*/
@Override
public void run( )
{
boolean keepRunning = true;
while ( keepRunning ) {
LinkedList< EntryQueueItem > items;
items = new LinkedList< EntryQueueItem >( );
// Wait for the next queue entry
EntryQueueItem item;
try {
item = this.queue.take( );
} catch ( InterruptedException e ) {
continue;
}
// Take everything else on the queue
this.queue.drainTo( items );
items.add( item );
// Process the items obtained from the queue, watching for null values as they will
// interrupt the task
final LinkedList< SystemLogEntry > okItems = new LinkedList< SystemLogEntry >( );
for ( EntryQueueItem i : items ) {
if ( i.entry == null ) {
keepRunning = false;
} else {
okItems.add( i.entry );
}
}
// If there's nothing to write, restart the loop (that usually means we're being
// terminated)
if ( okItems.isEmpty( ) ) {
continue;
}
// Try writing to the DB
boolean writeSuccess = false;
while ( ! writeSuccess ) {
try {
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
writeLogEntries( okItems );
}
} );
writeSuccess = true;
} catch ( DataAccessResourceFailureException e ) {
// Do nothing
} catch ( CannotCreateTransactionException e ) {
// Do nothing
}
if (! writeSuccess ) {
if ( !this.queue.isEmpty( ) ) {
// If there is stuff in the queue, abort writing.
// We might actually need to exit.
break;
}
try {
Thread.sleep( 2000 );
} catch ( InterruptedException e1 ) {
break;
}
}
}
}
}
private void writeLogEntries( List< SystemLogEntry > entries )
{
for ( SystemLogEntry entry : entries ) {
Long id = (Long) this.fLog.execute( entry.getComponent( ) , entry.getLevel( ).toString( ) ,
entry.getMessage( ) ).get( "entry_id" );
for ( ExceptionLog exc : entry.getException( ) ) {
Long eid = (Long) this.fException.execute( id , exc.getClassName( ) , exc.getMessage( ) ).get(
"entry_id" );
for ( StackTraceLog st : exc.getStack( ) ) {
this.fTrace.execute( eid , st.getLocation( ) , st.getFileName( ) , st.getLine( ) );
}
}
}
}
}

View file

@ -0,0 +1,61 @@
package com.deepclone.lw.beans.eventlog;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.interfaces.eventlog.LogWriter;
import com.deepclone.lw.interfaces.eventlog.Logger;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
import com.deepclone.lw.sqld.sys.SystemLogEntry;
/**
* The logger bean can be used to generate the various types of log entry generators. It
* communicates with a log writer to which entries produced by the generators will be flushed.
*
* @author tseeker
*
*/
public class LoggerBean
implements Logger
{
/** The log writer bean this logger pushes entries to */
private LogWriter logWriter;
/**
* Sets the log writer bean (DI)
*
* @param logWriter
* the log writer bean to push entries to
*/
@Autowired( required = true )
public void setLogWriter( LogWriter logWriter )
{
this.logWriter = logWriter;
}
/**
* Pushes a set of log entries to the log writer.
*
* @param entries
* the list of log entries to push
*/
void flush( List< SystemLogEntry > entries )
{
this.logWriter.addEntries( entries );
}
/* Documentation in Logger interface */
@Override
public SystemLogger getSystemLogger( String component )
{
return new SystemLoggerImpl( this , component );
}
}

View file

@ -0,0 +1,183 @@
package com.deepclone.lw.beans.eventlog;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import com.deepclone.lw.cmd.admin.logs.LogLevel;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
import com.deepclone.lw.sqld.sys.ExceptionLog;
import com.deepclone.lw.sqld.sys.StackTraceLog;
import com.deepclone.lw.sqld.sys.SystemLogEntry;
/**
* The system logger implementation generates {@link SystemLogEntry} instances, setting their fields
* to the proper values and parsing exceptions as required. When flushed it simply uses the logger
* bean's flush() method to push its contents to the log writer.
*
* @author tseeker
*/
class SystemLoggerImpl
implements SystemLogger
{
/** External (log4j) logger */
private final Logger l4j = Logger.getLogger( SystemLogger.class );
/** The logger bean */
private final LoggerBean logger;
/** The name of the component this system logger is used by */
private final String component;
/** The list of entries that have been created but that haven't been flushed to the logger bean */
private List< SystemLogEntry > entries = new LinkedList< SystemLogEntry >( );
/** Stores the logger bean reference and the component's name */
SystemLoggerImpl( LoggerBean logger , String component )
{
this.logger = logger;
this.component = component;
}
/* Documentation in SystemLogger interface */
@Override
public SystemLogger flush( )
{
synchronized ( this.entries ) {
if ( !this.entries.isEmpty( ) ) {
this.logger.flush( this.entries );
this.entries.clear( );
}
}
return this;
}
/* Documentation in SystemLogger interface */
@Override
public SystemLogger log( LogLevel level , String message )
{
this.toLog4j( level , message , null );
SystemLogEntry entry = this.makeEntry( level , message );
synchronized ( this.entries ) {
this.entries.add( entry );
}
return this;
}
/* Documentation in SystemLogger interface */
@Override
public SystemLogger log( LogLevel level , String message , Throwable exception )
{
this.toLog4j( level , message , exception );
SystemLogEntry entry = this.makeEntry( level , message );
this.parseException( entry , exception );
synchronized ( this.entries ) {
this.entries.add( entry );
}
return this;
}
/**
* Logs a message through Log4J (in addition to the DB log).
*
* @param level
* log level
* @param message
* message to log
* @param exception
* optional exception to log along with the message
*/
private void toLog4j( LogLevel level , String message , Throwable exception )
{
message = this.component + " - " + message;
switch ( level ) {
case DEBUG:
this.l4j.debug( message , exception );
break;
case ERROR:
this.l4j.error( message , exception );
break;
case INFO:
this.l4j.info( message , exception );
break;
case TRACE:
this.l4j.trace( message , exception );
break;
case WARNING:
this.l4j.warn( message , exception );
break;
}
}
/**
* Generates the basic contents of a log entry using a log level and message
*
* @param level
* the entry's log level
* @param message
* the entry's message
* @return a fully usable {@link SystemLogEntry} instance
*/
private SystemLogEntry makeEntry( LogLevel level , String message )
{
return new SystemLogEntry( level , this.component , message );
}
/**
* Parses the specified exception so that the ExceptionLog instance and associated data can
* later be retrieved.
*
* @param exception
* the exception to parse
*/
private void parseException( SystemLogEntry logEntry , Throwable exception )
{
do {
ExceptionLog excData;
excData = new ExceptionLog( logEntry , exception.getClass( ).getCanonicalName( ) , exception.getMessage( ) );
this.parseTrace( excData , exception );
exception = exception.getCause( );
} while ( exception != null );
}
/**
* This method converts an exception's stack trace into TracebackLog instances, adding them to
* the list of associated objects and returning the top-most item.
*
* @param logData
* the exception's log entry
* @param exception
* the exception whose stack trace is to be converted
*/
private void parseTrace( ExceptionLog logData , Throwable exception )
{
StackTraceElement[] trace = exception.getStackTrace( );
for ( int i = trace.length - 1 ; i >= 0 ; i-- ) {
StackTraceElement e = trace[ i ];
new StackTraceLog( logData , e.getClassName( ) + "." + e.getMethodName( ) , e.getFileName( ) , e
.getLineNumber( ) );
}
}
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<import resource="eventlog/admin-error-mail-bean.xml" />
<import resource="eventlog/log-cleaner-bean.xml" />
<import resource="eventlog/log-reader-bean.xml" />
<import resource="eventlog/log-writer-bean.xml" />
<import resource="eventlog/logger-bean.xml" />
</beans>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="adminErrorMail" class="com.deepclone.lw.beans.eventlog.AdminErrorMailBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="logCleaner" class="com.deepclone.lw.beans.eventlog.LogCleanerBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="logReader" class="com.deepclone.lw.beans.eventlog.LogReaderBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="logWriter" class="com.deepclone.lw.beans.eventlog.LogWriterBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="logger" class="com.deepclone.lw.beans.eventlog.LoggerBean" />
</beans>