/**
* Copyright (c) 2002-2013 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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.neo4j.kernel.impl.util;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import org.neo4j.helpers.Format;
public class StringLogger
{
public static final String DEFAULT_NAME = "messages.log";
public static final StringLogger SYSTEM =
new StringLogger( new PrintWriter( System.out ) );
private static final int DEFAULT_THRESHOLD_FOR_ROTATION_MB = 100;
private static final int NUMBER_OF_OLD_LOGS_TO_KEEP = 2;
private PrintWriter out;
private final Integer rotationThreshold;
private final File file;
@SuppressWarnings( "boxing" )
private StringLogger( String filename, int rotationThresholdMb )
{
this.rotationThreshold = rotationThresholdMb*1024*1024;
try
{
file = new File( filename );
if ( file.getParentFile() != null )
{
file.getParentFile().mkdirs();
}
instantiateWriter();
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
private void instantiateWriter() throws IOException
{
out = new PrintWriter( new FileWriter( file, true ) );
}
private StringLogger( PrintWriter writer )
{
this.out = writer;
this.rotationThreshold = null;
this.file = null;
}
private static final Map<String,StringLogger> loggers =
new HashMap<String, StringLogger>();
public static StringLogger getLogger( String storeDir )
{
return getLogger( storeDir, DEFAULT_THRESHOLD_FOR_ROTATION_MB );
}
public static StringLogger getLogger( String storeDir, int rotationThresholdMb )
{
if ( storeDir == null )
{
return SYSTEM;
}
String filename = defaultFileName( storeDir );
StringLogger logger = loggers.get( filename );
if ( logger == null )
{
logger = new StringLogger( filename, rotationThresholdMb );
loggers.put( filename, logger );
}
return logger;
}
private static String defaultFileName( String storeDir )
{
return new File( storeDir, DEFAULT_NAME ).getAbsolutePath();
}
public void logMessage( String msg )
{
logMessage( msg, false );
}
public void logMessage( String msg, Throwable cause )
{
logMessage( msg, cause, false );
}
public synchronized void logMessage( String msg, boolean flush )
{
ensureOpen();
out.println( time() + ": " + msg );
if ( flush )
{
out.flush();
}
checkRotation();
}
private String time()
{
return Format.date();
}
public synchronized void logMessage( String msg, Throwable cause, boolean flush )
{
ensureOpen();
out.println( time() + ": " + msg + " " + cause.getMessage() );
cause.printStackTrace( out );
if ( flush )
{
out.flush();
}
checkRotation();
}
private void ensureOpen()
{
/*
* Since StringLogger has instances in its own static map and HA graph db
* does internal restarts of the database the StringLogger instances are kept
* whereas the actual files can be removed/replaced, making the PrintWriter
* fail at writing stuff and also swallowing those exceptions(!). Since we
* have this layout of static map of loggers we'll have to reopen the PrintWriter
* in such occasions. It'd be better to tie each StringLogger to a GraphDatabaseService.
*/
if ( out.checkError() )
{
out.close();
try
{
instantiateWriter();
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
}
private void checkRotation()
{
if ( rotationThreshold != null && file.length() > rotationThreshold.intValue() )
{
doRotation();
}
}
private void doRotation()
{
out.close();
moveAwayFile();
try
{
instantiateWriter();
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
/**
* Will move:
* messages.log.1 -> messages.log.2
* messages.log -> messages.log.1
*
* Will delete (if exists):
* messages.log.2
*/
private void moveAwayFile()
{
File oldLogFile = new File( file.getParentFile(), file.getName() + "." + NUMBER_OF_OLD_LOGS_TO_KEEP );
if ( oldLogFile.exists() )
{
oldLogFile.delete();
}
for ( int i = NUMBER_OF_OLD_LOGS_TO_KEEP-1; i >= 0; i-- )
{
oldLogFile = new File( file.getParentFile(), file.getName() + (i == 0 ? "" : ("." + i)) );
if ( oldLogFile.exists() )
{
oldLogFile.renameTo( new File( file.getParentFile(), file.getName() + "." + (i+1) ) );
}
}
}
public void flush()
{
out.flush();
}
public synchronized static void close( String storeDir )
{
StringLogger logger = loggers.remove( defaultFileName( storeDir ) );
if ( logger != null )
{
logger.out.close();
}
}
}