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
}
}
}
}