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 */ 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: