/*
* Copyright (C) 2008 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.logger;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import static com.google.common.base.Preconditions.checkState;
/**
* Keeps log lines in memory until a call to {@link #flush(LoggerFactory)} or the JVM exits.
*
* @author Laurent Caillette
*/
/*package*/ class DeferringLoggerFactory extends LoggerFactory {
/**
* Synchronize everything on this one. Might be suboptimal but who cares?
*/
private static final Object LOCK = new Object() ;
private static final List< NamedLogRecord > LOG_RECORDS = Lists.newArrayList() ;
private static final Map< String, RecordingLogger > LOGGERS = Maps.newHashMap() ;
@SuppressWarnings( { "StaticNonFinalField" } )
private static boolean open = true ;
@Override
protected Logger doGetLogger( final String name ) {
synchronized( LOCK ) {
checkState( open ) ;
final RecordingLogger existing = LOGGERS.get( name ) ;
if( existing == null ) {
final RecordingLogger newRecordingLogger = new RecordingLogger( name ) ;
LOGGERS.put( name, newRecordingLogger ) ;
return newRecordingLogger ;
} else {
return existing ;
}
}
}
/**
* Call this only once.
*
* @param loggerFactory a non-null object. The {@link Logger} instances it provides must
* be instances of {@link org.novelang.logger.AbstractLogger}.
*/
@SuppressWarnings( { "ThrowableResultOfMethodCallIgnored" } )
public static void flush( final LoggerFactory loggerFactory ) {
synchronized( LOCK ) {
checkState( open ) ;
for( final NamedLogRecord logRecord : LOG_RECORDS ) {
final AbstractLogger sink = ( AbstractLogger )
loggerFactory.doGetLogger( logRecord.getLoggerName() ) ;
sink.log( logRecord.getLevel(), logRecord.getMessage(), logRecord.getThrowable() ) ;
}
LOGGERS.clear() ;
LOG_RECORDS.clear() ;
open = false ;
}
}
static {
Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
@Override
public void run() {
shutdownFlush() ;
}
}, "" ) ) ;
}
@SuppressWarnings( { "UseOfSystemOutOrSystemErr", "ThrowableResultOfMethodCallIgnored" } )
private static void shutdownFlush() {
synchronized( LOCK ) {
if( open ) {
final PrintStream printStream = System.err ;
printStream.println( "Flushing in-memory log..." ) ;
for( final NamedLogRecord logRecord : LOG_RECORDS ) {
final StringBuilder logLineBuilder = new StringBuilder() ;
logLineBuilder.append( String.format( "%-5s", logRecord.getLevel() ) ) ;
logLineBuilder.append( " [" ) ;
logLineBuilder.append( logRecord.getThreadName() ) ;
logLineBuilder.append( "] " ) ;
logLineBuilder.append( logRecord.getLoggerName() ) ;
logLineBuilder.append( " - " ) ;
logLineBuilder.append( logRecord.getMessage() ) ;
if( logRecord.hasThrowable() ) {
logLineBuilder.append( " " ) ;
logLineBuilder.append( logRecord.getThrowable().toString() ) ;
}
printStream.println( logLineBuilder.toString() ) ;
}
}
}
}
private static class RecordingLogger extends AbstractLogger {
private final String loggerName ;
private RecordingLogger( final String loggerName ) {
this.loggerName = loggerName ;
}
@Override
protected void log( final Level level, final String message, final Throwable throwable ) {
synchronized( LOCK ) {
final AbstractLogger maybeDelegate = ( AbstractLogger ) LoggerFactory.getLogger( loggerName );
if( maybeDelegate == this ) {
// Still using this factory.
checkState( open ) ;
LOG_RECORDS.add( new NamedLogRecord(
loggerName, Thread.currentThread().getName(), level, message, throwable ) ) ;
} else {
// Switched to another factory.
maybeDelegate.log( level, message, throwable ) ;
}
}
}
@Override
public String getName() {
return loggerName ;
}
@Override
public boolean isTraceEnabled() {
return true ;
}
@Override
public boolean isDebugEnabled() {
return true ;
}
@Override
public boolean isInfoEnabled() {
return true ;
}
}
}