package gov.lbl.netlogger; /* * Copyright (c) 2004, The Regents of the University of California, through * Lawrence Berkeley National Laboratory (subject to receipt of any required * approvals from the U.S. Dept. of Energy). All rights reserved. */ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.TimeZone; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * This class lets you easily construct a set of typed (name, value) pairs * that formats itself as a CEDPS Best Practices log message. * <p> * Name and value pairs are added into the record with add() methods * which, by virtue of returning the newly modified LogMessage instance, * can be chained together. For example: * </p><blockquote> * <code>LogMessage message = * new LogMessage("my.event").add("my int",3).add("my float",4.0);</code> * </blockquote> * <p> * The user can set the timestamp to something other than the * time of the call by calling setTimeStamp{Millis,Nanos}() as part of the chain. * </p><p> * To format the message, call toString(). The output format is * <a href="http://www.cedps.net/wiki/index.php/LoggingBestPractices">CEDPS "Best Practices" format</a>. * </p><p> * Since the addition of the nanosecond timestamp (which is rounded * down to microseconds, and no I don't want to discuss it), this class * requires Java 1.5 * </p> * * @author Dan Gunter dkgunter@lbl.gov * @author Wolfgang Hoschek whoschek@lbl.gov * @version $Revision: 1.8 $ */ public class LogMessage { // Variables private final StringBuffer buf = new StringBuffer(256); // set initial capacity for efficiency/memory trade-off private static long micro0, nano0, micro1; private long micro2; // Static Variables private static String timeString = null; private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); private static final GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); private static Lock timeStringLock = new ReentrantLock(); // Constants public static final String APPENDER = "NETLOGGER"; public static final String EVENT_KW = "event"; public static final String DATE_KW = "ts"; public static final String FAKE_DATE = "1999-01-01T11:59:59.999999Z"; private final int dateStart = DATE_KW.length() + 1; private final int dateEnd = dateStart + FAKE_DATE.length() - 8; private final int usecStart = dateEnd + 1; private final int usecEnd = dateEnd + 7; public static final String LEVEL_KW = "level="; private static final char[] DIGIT = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; /** * Create a new LogMessage at the current time with a given event name. * <p/> * The timestamp is set at creation time, but can be changed later * with <code>setTimeStampMillis</code> or <code>setTimeStampNanos</code>. * * @param eventName Name of this logging event. * @see #setTimeStampMillis * @see #setTimeStampNanos */ public LogMessage(String eventName) { add(DATE_KW, FAKE_DATE); add(EVENT_KW, eventName); long nano1 = System.nanoTime(); /* calculate timestamp in microseconds */ micro2 = (nano1 - nano0) / 1000 + micro0; } /** * Add a string. * * @return Self-reference, so calls can be chained */ public LogMessage add(String key, String value) { buf.append(key); buf.append("="); buf.append(value); buf.append(" "); return this; } /** * Add an int. * * @return Self-reference, so calls can be chained */ public LogMessage add(String key, int value) { buf.append(key); buf.append("="); buf.append(value); buf.append(" "); return this; } /** * Add a long. * * @return Self-reference, so calls can be chained */ public LogMessage add(String key, long value) { buf.append(key); buf.append("="); buf.append(value); buf.append(" "); return this; } /** * Add a float. * * @return Self-reference, so calls can be chained */ public LogMessage add(String key, float value) { buf.append(key); buf.append("="); buf.append(value); buf.append(" "); return this; } /** * Add a double. * * @return Self-reference, so calls can be chained */ public LogMessage add(String key, double value) { buf.append(key); buf.append("="); buf.append(value); buf.append(" "); return this; } /** * Set the timestamp from milliseconds * returned by System.currentTimeMillis(). * * @return 'this' so we can chain */ public LogMessage setTimeStampMillis(long millis) { micro2 = millis * 1000; return this; } /** * Set the timestamp from nanoseconds * returned by System.nanoTime(). * * @return 'this' so we can chain */ public LogMessage setTimeStampNanos(long nano1) { micro2 = (nano1 - nano0) / 1000 + micro0; return this; } /** * Format a message in CEDPS Best Practices format. * * @return Formatted message string * @see <a href="http://www.cedps.net/wiki/index.php/LoggingBestPractices">CEDPS "Best Practices" format</a> */ public String toString() { if (micro2 > 0) { addTimeStamp(); micro2 = 0; } return buf.toString(); } /** * Add a timestamp to the message. */ private void addTimeStamp() { // re-use or re-set whole seconds if (micro2 / 1000000L != micro1 / 1000000L) { timeStringLock.lock(); timeString = format.format(new Date(micro2 / 1000L)); timeStringLock.unlock(); micro1 = micro2; } buf.replace(dateStart, dateEnd, timeString); // add fractional time (microseconds) long div, frac; int i; frac = micro2 % 1000000L; for (i = 0, div = 100000L; i < 6; div = div / 10, i++) { long n = frac / div; buf.setCharAt(usecStart + i, DIGIT[(int) n]); frac -= n * div; } } //=============================================================== // Log4J compatibility (contributed by Wolfgang Hoschek) //=============================================================== /** * Static class initializer. * * Make it so that log4j.jar is a compile time requirement, * but not a runtime requirement */ static { try { // check if log4j is present Class.forName("org.apache.log4j.spi.Filter"); // executed only if log4j is present Log4jFilter.init(); } catch (ClassNotFoundException e) { // This warning might mess up daemon processes, // so it's commented out by default System.err.println( "Warning: Cannot find log4j " + "(org.apache.log4j.spi.Filter), " + "continuing.."); } // set calendar of formatter: otherwise no UTC!! format.setCalendar(calendar); // init base nanosecond and millisecond time long ms = System.currentTimeMillis(); nano0 = System.nanoTime(); micro0 = micro1 = ms * 1000L; timeString = format.format(new Date(ms)); } /** * In log4j, ignore all messages not specifically directed * at this appender. */ private static final class Log4jFilter extends org.apache.log4j.spi.Filter { public static void init() { Enumeration loggers = org.apache.log4j.Logger .getRootLogger() .getLoggerRepository() .getCurrentLoggers(); while (loggers.hasMoreElements()) { org.apache.log4j.Logger logger = (org.apache.log4j.Logger) loggers.nextElement(); if (logger.getAppender(APPENDER) != null) { logger.getAppender(APPENDER).addFilter(new Log4jFilter()); } } } public int decide(org.apache.log4j.spi.LoggingEvent event) { if (event.getMessage() instanceof LogMessage) { return NEUTRAL; // let message pass through } else { return DENY; // ignore all non-netlogger messages } } } }