/** *

* Copyrights: No copyrights. Use however you want. * *

* Warranty: Comes with absolutely no * warranty. As Dijkstra said, "I have only proved the code * correct, I haven't actually tested it." * *

* Credits: Credits are appreciated, not required. */ package edu.gwu.logging; import java.net.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.ObjectOutputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.logging.LogRecord; import java.util.logging.Handler; import java.util.logging.StreamHandler; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; /** * An adapter for using Log4j Chainsaw with JDK logging infrastructure. * The JDK logging publishes {@link LogRecord} objects, this adapter * creates a LoggingEvent from a LogRecord and writes it to Chainsaw socket. * * @author Amrinder Arora */ public class ChainsawHandler extends StreamHandler { /** Number of consecutive errors, exceeding which this handler is disabled */ public static int MAX_ERRORS = 10; /** Number of consecutive errors that have occurred since starting this handler */ protected static int numErrors = 0; /** Is this handler disabled? */ protected static boolean disabled = false; /** Chainsaw socket */ protected static Socket targetSocket = null; /** Chainsaw Inet address String representation */ protected static String targetInetAddressProp = "localhost"; /** Chainsaw inet address (value derived from {@link #targetInetAddressProp}) */ protected static InetAddress targetInetAddress = null; /** Chainsaw port number, as a String */ protected static String chainsawPortProp = "4445"; /** MAX_ERRORS, as a String */ protected static String MAX_ERRORS_prop = "10"; /** Chainsaw port number (value derived from {@link #chainsawPortProp}) */ protected static int chainsawPort = 4445; /** * Object OutputStream that this ChainsawHandler writes to. A chainsaw GUI * application is hopefully listening at the other end of this stream. */ protected static ObjectOutputStream objectOutputStream = null; /** * Creates an instance of ChainsawHandler object. */ public ChainsawHandler () { System.err.println ("An instance of Chainsaw Handler is being created"); try { renewTarget (); } catch (Throwable t) { System.err.println ("Target was NOT successfully lookedup"); } } /** * Renews the object output stream. It does the following steps:

    *
  1. Closes the resources using {@link #closeResources}. *
  2. Resolves the targetInetAddress from targetInetAddressProp. *
  3. Resolves the chainsawPort from chainsawPortProp. *
  4. Gets the targetSocket and objectOutputStream from * targetInetAddress and chainsawPort. */ protected static void renewTarget () { closeResources(); // Gets the targetInetAddress try { if ("localhost".equalsIgnoreCase(targetInetAddressProp)) { targetInetAddress = InetAddress.getLocalHost(); } else if (Character.isDigit (targetInetAddressProp.charAt (0))) { targetInetAddress = InetAddress.getByAddress(getBytesFromIPAddress (targetInetAddressProp)); } else { targetInetAddress = InetAddress.getByName(targetInetAddressProp); } } catch (Throwable t) { System.err.println ("Exception in getting the target Inet address: " + t); try { targetInetAddress = InetAddress.getLocalHost(); System.err.println ("Set the target Inet address to localhost"); } catch (Exception e) { System.err.println ("I was not able to resolve the inetaddress, and the back up failed too. Sorry it didnt work out."); throw new RuntimeException ("InetAddress could not be resolved"); } } // Gets the chainsawPort try { chainsawPort = Integer.parseInt (chainsawPortProp); } catch (Throwable t) { System.err.println ("Exception in getting the chainsaw port: " + t); chainsawPort = 4445; } // Obtains the socket using targetInetaddress and chainsawPort try { targetSocket = new Socket (targetInetAddress, chainsawPort); objectOutputStream = new ObjectOutputStream (targetSocket.getOutputStream()); } catch (Exception e) { System.err.println ("Exception initializing socket, output stream or object output stream"); throw new RuntimeException (e); } } /** * Publishes this log record. * *

    * If there is a problem in publishing this log record, it tries to * renew the object output stream using * {@link #renewTarget}, and increments the number of errors.
    * If this log record is published successfully, {@link #numErrors} is reset to 0, * since it holds the number of consecutive errors. * * @param logRecord The LogRecord to be published */ public void publish (LogRecord logRecord) { try { if (disabled) return; // System.out.println ("Writing: '" + logRecord + "' on the chainsaw socket"); // (You cant log logging of a log. You would really be a log if you did that.) LoggingEvent loggingEvent = getLoggingEvent (logRecord); objectOutputStream.writeObject (loggingEvent); numErrors = 0; } catch (Throwable e) { // Enclose the exception handling in another try catch block // to make sure publish() does not throw exception back at user try { // Increments the number of consecutive errors numErrors++; System.err.println (e + "(Error # " + numErrors + ") writing this logRecord: " + logRecord); // Gives up if too many errors if (numErrors > MAX_ERRORS) { System.err.println ("Number of consecutive errors has exceeded threshold, disabling this handler"); disabled = true; } // If this is the first error, prints the stack trace of the exception // next consecutive errors will be ignored. if (numErrors == 1) e.printStackTrace(); // Attempts to renew the target (maybe GUI is running now/network // is now up, etc, other retry conditions) renewTarget (); } catch (Throwable ignored) { // ignores this Throwable, doesn't even log it, to give adequate guarantee that // publish() does not throw exception back at user } } } /** * Converts a {@link java.util.logger.LogRecord} to a * {@link org.apache.log4j.spi.LoggingEvent} */ protected LoggingEvent getLoggingEvent (LogRecord logRecord) { String fqn = logRecord.getLoggerName(); Logger logger = Logger.getLogger (fqn); Level level = (Level) levelConversionMap.get (logRecord.getLevel()); long timestamp = logRecord.getMillis(); Object message = logRecord.getMessage(); Throwable t = logRecord.getThrown(); return new LoggingEvent (fqn, logger, timestamp, level, message, t); } /** * Closes the resources held by this handler (socket and object output stream). */ public static void closeResources() { if (objectOutputStream != null) { try {objectOutputStream.close();} catch (Throwable t) {System.err.println ("Exception closing oos");} } if (targetSocket != null) { try {targetSocket.close();} catch (Throwable t) {System.err.println ("Exception closing socket");} } } /** * A level conversion map is used to translate levels from one logging * infrastructure to another logging infrastructure. */ public static Map levelConversionMap = new HashMap(); static { levelConversionMap.put (java.util.logging.Level.OFF, Level.OFF); levelConversionMap.put (java.util.logging.Level.SEVERE, Level.FATAL); levelConversionMap.put (java.util.logging.Level.WARNING, Level.WARN); levelConversionMap.put (java.util.logging.Level.INFO, Level.INFO); levelConversionMap.put (java.util.logging.Level.CONFIG, Level.INFO); levelConversionMap.put (java.util.logging.Level.FINE, Level.DEBUG); levelConversionMap.put (java.util.logging.Level.FINER, Level.DEBUG); levelConversionMap.put (java.util.logging.Level.FINEST, Level.DEBUG); levelConversionMap.put (java.util.logging.Level.ALL, Level.ALL); } /** * Sets the chainsaw port. */ public void setChainsawPortProp (String argChainsawPortProp) { System.err.println ("setting chainsawPortProp to: " + argChainsawPortProp); this.chainsawPortProp = argChainsawPortProp; } /** * Sets the level. */ public void setLevel (String argLevel) { System.err.println ("setting Level to: " + argLevel); this.chainsawPortProp = argLevel; } /** * Initializes a chainsaw handler with configurable properties. * *

    * Possible values for inetAddress property are the following: *

    */ public static void initialize (InputStream instream) throws IOException { System.err.println ("initialize(): begin"); Properties initProperties = new Properties(); initProperties.load (instream); // Assigns the inetAddressProp, if specified in properties String newProp = initProperties.getProperty ("inetAddress"); if (newProp != null) targetInetAddressProp = newProp; // Assigns the chainsawPortProp, if specified in properties newProp = initProperties.getProperty ("port"); if (newProp != null) chainsawPortProp = newProp; // Assigns the max errors, if specified in properties newProp = initProperties.getProperty ("maxErrors"); if (newProp != null) { MAX_ERRORS_prop = newProp; // Sets the MAX_ERRORS try { MAX_ERRORS = Integer.parseInt (MAX_ERRORS_prop); System.out.println ("MAX_ERRORS: " + MAX_ERRORS); } catch (Exception e) { System.err.println ("Exception initializing MAX_ERRORS, resetting to 10"); MAX_ERRORS = 10; } } // Initializes the level conversion if any override properties are specified initializeLevelConversions(initProperties); // renews the object output stream renewTarget (); } /** * Initializes the level conversion map from the given properties. */ protected static void initializeLevelConversions (Properties levelProperties) { // Gets the log4j (Integer -> Level) Map Map log4jLevelsMap = getLog4jInteger2LevelsMap (); // Gets the jdk (Integer -> Level) Map Map jdkLevelsMap = getJdkInteger2LevelsMap (); levelConversionMap.put (java.util.logging.Level.OFF, Level.OFF); levelConversionMap.put (java.util.logging.Level.ALL, Level.ALL); Enumeration propertyNames = levelProperties.propertyNames(); while (propertyNames.hasMoreElements()) { try { String levelProperty = (String) propertyNames.nextElement(); if (levelProperty.startsWith ("level.jdk.")) { String jdkLevelProp = levelProperty.substring (10); String log4jLevelProp = levelProperties.getProperty (levelProperty); if (log4jLevelProp.startsWith ("level.log4j.")) { log4jLevelProp = log4jLevelProp.substring (12); // gets the Integer values from Strings Integer jdkLevelInt = new Integer (jdkLevelProp); Integer log4jLevelInt = new Integer (log4jLevelProp); // gets the Level values from Integers java.util.logging.Level jdkLevel = (java.util.logging.Level) (jdkLevelsMap.get (jdkLevelInt)); Level log4jLevel = (Level) (log4jLevelsMap.get (log4jLevelInt)); levelConversionMap.put (jdkLevel, log4jLevel); } } } catch (Throwable t) { System.err.println ("Exception in reading a level conversion property " + t); } } } /** * Gets byte array given a String of IP address. * Input string is assumed to be of this form: 192.168.1.1 * If it has less than 3 periods, an exception may be thrown. */ protected static byte[] getBytesFromIPAddress (String ipAddress) { int indexOfPeriod = ipAddress.indexOf ("."); String byte1 = ipAddress.substring (0,indexOfPeriod); ipAddress = ipAddress.substring (indexOfPeriod + 1); indexOfPeriod = ipAddress.indexOf ("."); String byte2 = ipAddress.substring (0,indexOfPeriod); ipAddress = ipAddress.substring (indexOfPeriod + 1); indexOfPeriod = ipAddress.indexOf ("."); String byte3 = ipAddress.substring (0,indexOfPeriod); String byte4 = ipAddress.substring (indexOfPeriod + 1); int i1 = Integer.parseInt (byte1); int i2 = Integer.parseInt (byte2); int i3 = Integer.parseInt (byte3); int i4 = Integer.parseInt (byte4); return new byte[] {(byte) i1, (byte) i2, (byte) i3, (byte) i4}; } /** * Prints the Log4j and JDK levels to the system output. */ public static void printLevelsHelp () { System.out.println ("log4j levels: "); System.out.println ("level.OFF: " + Level.OFF.toInt()); System.out.println ("level.FATAL: " + Level.FATAL.toInt()); System.out.println ("level.ERROR: " + Level.ERROR.toInt()); System.out.println ("level.WARN: " + Level.WARN.toInt()); System.out.println ("level.INFO: " + Level.INFO.toInt()); System.out.println ("level.DEBUG: " + Level.DEBUG.toInt()); System.out.println ("level.ALL: " + Level.ALL.toInt()); System.out.println ("\nJDK levels: "); System.out.println ("level.OFF: " + java.util.logging.Level.OFF.intValue()); System.out.println ("level.SEVERE: " + java.util.logging.Level.SEVERE.intValue()); System.out.println ("level.WARNING: " + java.util.logging.Level.WARNING.intValue()); System.out.println ("level.INFO: " + java.util.logging.Level.INFO.intValue()); System.out.println ("level.CONFIG: " + java.util.logging.Level.CONFIG.intValue()); System.out.println ("level.FINE: " + java.util.logging.Level.FINE.intValue()); System.out.println ("level.FINER: " + java.util.logging.Level.FINER.intValue()); System.out.println ("level.FINEST: " + java.util.logging.Level.FINEST.intValue()); System.out.println ("level.ALL: " + java.util.logging.Level.ALL.intValue()); } /** * Gets a map of Integer to log4j Level objects. */ protected static Map getLog4jInteger2LevelsMap () { Map log4jLevels = new HashMap(); // populating log4j levels log4jLevels.put (new Integer (Level.OFF.toInt()), Level.OFF); log4jLevels.put (new Integer (Level.FATAL.toInt()), Level.FATAL); log4jLevels.put (new Integer (Level.ERROR.toInt()), Level.ERROR); log4jLevels.put (new Integer (Level.WARN.toInt()), Level.WARN); log4jLevels.put (new Integer (Level.INFO.toInt()), Level.INFO); log4jLevels.put (new Integer (Level.DEBUG.toInt()), Level.DEBUG); log4jLevels.put (new Integer (Level.ALL.toInt()), Level.ALL); return log4jLevels; } /** * Gets a map of Integer to jdk Level objects. */ protected static Map getJdkInteger2LevelsMap () { Map jdkLevels = new HashMap(); // populating JDK levels jdkLevels.put (new Integer (java.util.logging.Level.OFF.intValue()), java.util.logging.Level.OFF); jdkLevels.put (new Integer (java.util.logging.Level.SEVERE.intValue()), java.util.logging.Level.SEVERE); jdkLevels.put (new Integer (java.util.logging.Level.WARNING.intValue()), java.util.logging.Level.WARNING); jdkLevels.put (new Integer (java.util.logging.Level.INFO.intValue()), java.util.logging.Level.INFO); jdkLevels.put (new Integer (java.util.logging.Level.CONFIG.intValue()), java.util.logging.Level.CONFIG); jdkLevels.put (new Integer (java.util.logging.Level.FINE.intValue()), java.util.logging.Level.FINE); jdkLevels.put (new Integer (java.util.logging.Level.FINER.intValue()), java.util.logging.Level.FINER); jdkLevels.put (new Integer (java.util.logging.Level.FINEST.intValue()), java.util.logging.Level.FINEST); jdkLevels.put (new Integer (java.util.logging.Level.ALL.intValue()), java.util.logging.Level.ALL); return jdkLevels; } /** * Enables this handler. */ public static void enable () { disabled = false; numErrors = 0; } }