Today, a subset of what I was working on was to provide a way to debug a statistics generator I'm writing. For some reason, the more elaborate log4j-based logging class I use in production mode would not work from my JUnit testing, so I threw this one together. This is not a serious attempt to replace that real facility, however, but just something I had to do by reason of an externally imposed impediment I won't bore you with.
Inside this codeIn terms of beginning Java samples you have:
|
|
Eclipse tip: Testing with main() or JUnit
When you create a class that you'll want to test lightly, be sure to tell
Eclipse to create the If you're going to test it with JUnit, this isn't a problem, but there, be sure to use Eclipse's JUnit test class wizard to create your test. And be sure you have added the JUnit library using Build Path. |
package com.etretatlogiciels.samples.logging;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.text.SimpleDateFormat;
/**
* Quick and dirty replacement for log4j. The logging levels are slightly
* different from log4j. I used TRACE, for instance, to profile the actual
* sequence of method calls of a facility I was implementing and it worked
* perfectly for this.
*
* - DEBUG
* - TRACE
* - INFO
* - WARN
* - ERROR
*
* @author Russell Bateman, April 2009
*/
public class JUnitLogger
{
public static int DEBUG = 1, TRACE = 2, INFO = 3, WARN = 4, ERROR = 5;
public static int TO_LOGFILE = 0, TO_CONSOLE = 1;
private String classname = "[unknown class]";
private int level = DEFAULT_LEVEL;
private int target = DEFAULT_TARGET;
private static int DEFAULT_TARGET = TO_LOGFILE;
private static int DEFAULT_LEVEL = INFO;
private static String DEFAULT_PATH = "/tmp/junit/logs";
private static String DEFAULT_NAME = "JUnit";
private static String FILENAME = null;
static
{
// name the logger output file...
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
FILENAME = DEFAULT_NAME + "-"
+ sdf.format( cal.getTime() )
+ ".log";
/* TODO: This caves in if the privileges aren't propitious for us on
* "/tmp/junit/logs". If this code gets used for real work, it's going
* to have to be redone including to support Windows. It's also complete
* crap: rewrite it someday if and when it begins to matter.
*/
File whole = new File( DEFAULT_PATH );
if( !whole.exists() )
{
File junit = new File( "/tmp/junit" );
if( !junit.exists() )
junit.mkdir();
File logs = new File( DEFAULT_PATH );
if( !logs.exists() )
logs.mkdir();
}
}
/**
* Create a new logger and give it a name to display.
*
* @param callingClass—for use in the logging message (Class)
*/
public JUnitLogger( Class< ? > callingClass )
{
if( callingClass != null )
this.classname = callingClass.getName();
}
/**
* Bounce the default logging file (delete it).
*/
public static void bounceJUnitLogger()
{
File f = new File( makeLogPath() );
if( f != null )
f.delete();
}
/**
* Change the default target of all subsequent logger instances from going to
* a file to going to the console.
*/
public static void defaultToUseConsole() { DEFAULT_TARGET = TO_CONSOLE; }
/**
* Re-establish the level of logging in force or return to default setting.
*
* @param newLevel—new level or 0 to return to default (int)
*/
public static void setDefaultLevel( int newLevel )
{
if( inrange( DEBUG, newLevel, ERROR ) )
DEFAULT_LEVEL = newLevel;
else if( newLevel == 0 )
DEFAULT_LEVEL = INFO;
}
/**
* Force this logger instance to write to console instead of to file if true
* or back to the file if false.
*
* @param yesno—true if messages are to be output to the console (boolean)
*/
public void useTarget( int tgt )
{
if( inrange( TO_LOGFILE, tgt, TO_CONSOLE ) )
this.target = tgt;
}
/**
* Change the logging level; this may be convenient, but it also
* may be pernicious, so watch out.
*
* @param newLevel—at which to log messages from now on (int)
*/
public void changeLevel( int newLevel )
{
if( inrange( DEBUG, newLevel, ERROR ) )
this.level = newLevel;
}
/**
* Establish the logfile path different from the one in force or return to
* the default. The path must exists and the process have privileges or
* logging will fail.
*
* TODO: If this becomes real code (unlikely), then fix this unpardonably
* lazy attitude about the path existing and being accessible.
*
* @param path—new path or nil to return to default (String)
*/
public static void setDefaultPath( String path )
{
if( path == null )
{
DEFAULT_PATH = "/tmp/junit/logs";
return;
}
File f = new File( path );
if( !f.isDirectory() )
return;
DEFAULT_PATH = path;
}
/**
* Insert a blank line in the log file. You saw it here first!
*/
public void insertBlankLine()
{
writeOutput( "" );
}
/**
* Insert a blank line in the log file. It will do this only if the
* current logging level meets the threshold.
*
* @param threshold—below which this action will occur (int)
*/
public void insertBlankLine( int threshold )
{
if( this.level <= threshold )
writeOutput( "" );
}
/**
* Insert a horizontal ruler in the log file. You saw it here first!
* You can supply a string of characters to use as a horizontal ruler.
*/
public void insertHorizontalRuler( String horizontalRule )
{
writeOutput( horizontalRule );
}
/**
* Insert a horizontal ruler in the log file. It will do this only if the
* current logging level meets the threshold.
*
* @param threshold—below which this action will occur (int)
*/
public void insertHorizontalRuler( int threshold, String horizontalRule )
{
if( this.level <= threshold )
writeOutput( horizontalRule );
}
public boolean isDebugEnabled() { return( this.level <= DEBUG ); }
public boolean isTraceEnabled() { return( this.level <= TRACE ); }
public boolean isInfoEnabled() { return( this.level <= INFO ); }
public boolean isWarnEnabled() { return( this.level <= WARN ); }
public boolean isErrorEnabled() { return( this.level <= ERROR ); }
public void debug( String msg ) { doLogger( DEBUG, msg ); }
public void trace( String msg ) { doLogger( TRACE, msg ); }
public void info ( String msg ) { doLogger( INFO, msg ); }
public void warn ( String msg ) { doLogger( WARN, msg ); }
public void error( String msg ) { doLogger( ERROR, msg ); }
/* ========================================================================
* Private methods
*/
private static String manufactureDateAndTimeStamp()
{
// return "2009-04-30 19:32:59.731"
Calendar cal = Calendar.getInstance();
long milliseconds = cal.get( Calendar.MILLISECOND );
String year = "" + cal.get( Calendar.YEAR );
String month = "" + cal.get( Calendar.MONTH );
String day = "" + cal.get( Calendar.DATE );
String hour = "" + cal.get( Calendar.HOUR_OF_DAY );
String minute = "" + cal.get( Calendar.MINUTE );
String second = "" + cal.get( Calendar.SECOND );
String millis = "" + milliseconds;
/* Uniform width!
* We found that despite HOUR_OF_DAY in place of DAY and all other
* things being equal, this screws up and yields narrower widths
* like single digits before 10 o'clock, etc. So, we ensure they
* are double-wide. Also, we compensate for millisecond values not
* occupying three places.
*/
if( hour.length() == 1 )
hour = " " + hour;
if( minute.length() == 1 )
minute = "0" + minute;
if( second.length() == 1 )
second = "0" + second;
if( milliseconds < 10 )
millis = "00" + millis;
else if( milliseconds < 100 )
millis = "0" + millis;
String timestamp = year
+ "-" + month
+ "-" + day
+ " " + hour
+ ":" + minute
+ ":" + second
+ "." + millis;
return timestamp;
}
private static boolean inrange( int lo, int x, int hi )
{
return( x >= lo && x <= hi );
}
private static String makeLogPath()
{
return DEFAULT_PATH + "/" + FILENAME;
}
private String formatOutput( String msg )
{
String output = manufactureDateAndTimeStamp();
output += " " + textForLevel( this.level );
output += " " + this.classname;
output += ": " + msg;
return output;
}
private synchronized void writeOutput( String output )
{
if( this.target == TO_CONSOLE )
{
System.out.println( output );
return;
}
try
{
// open file and append...
FileWriter f = new FileWriter( makeLogPath(), true );
f.write( output + "\n" );
f.close();
}
catch( IOException e )
{
e.printStackTrace();
}
}
private final void doLogger( int threshold, String msg )
{
if( this.level <= threshold )
writeOutput( formatOutput( msg ) );
}
private String textForLevel( int which )
{
switch( which )
{
case 1 : return " DEBUG ";
case 2 : return " TRACE ";
case 3 : return " INFO ";
case 4 : return " WARN ";
case 5 : return " ERROR ";
default : return " [unknown level] ";
}
}
public static void main( String[] args )
{
JUnitLogger.bounceJUnitLogger();
//JUnitLogger.defaultToUseConsole();
JUnitLogger.setDefaultLevel( JUnitLogger.TRACE );
JUnitLogger log = new JUnitLogger( JUnitLogger.class );
// the quickie test will output just this one line...
log.trace( "Do stuff in main()" );
}
}
// vim: set tabstop=2 shiftwidth=2 noexpandtab: