package edu.isi.ikcap.workflows.util.logging;
/*
* The following is the original copy right from gov.lbl.netlogger.LogMessage
* 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.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.locks.*;
/**
* This is a modification of gov.lbl.netlogger.LogMessage
*
* 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.
* <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
* @author Paul Groth pgroth@isi.edu
*/
public class EventLogMessage {
// 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 = "KEYVALUE";
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;
@SuppressWarnings("unused")
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'};
private static Escape escape = new Escape( "\"" , '\\' , false );
/**
* Create a new LogMessage at the current time with a given event name.
*
* 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
*/
protected EventLogMessage(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 EventLogMessage add(String key, String value) {
buf.append(key);
buf.append("=");
buf.append(value);
buf.append(" ");
return this;
}
/**
* Add a string. Strings automatically have quotes around them
* and are escaped
* @return Self-reference, so calls can be chained
*/
public EventLogMessage addWQ (String key, String value)
{
buf.append(key);
buf.append("=");
buf.append("\"");
buf.append(escape.escape(value));
buf.append("\"");
buf.append(" ");
return this;
}
/**
* Add an int.
* @return Self-reference, so calls can be chained
*/
public EventLogMessage 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 EventLogMessage 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 EventLogMessage 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 EventLogMessage add(String key, double value) {
buf.append(key);
buf.append("=");
buf.append(value);
buf.append(" ");
return this;
}
/**
* Add a key,value pair
* The result looks like: key = (pairKey, pairValue)
* @param key
* @param pairKey
* @param pairValue
*/
public EventLogMessage addPair (String key, String pairKey, String pairValue)
{
buf.append(key);
buf.append("=");
buf.append("(");
buf.append(pairKey);
buf.append(",");
buf.append(pairValue);
buf.append(")");
buf.append(" ");
return this;
}
public EventLogMessage addTime (String key, long timeInMillis)
{
buf.append(key);
buf.append("=");
buf.append(format.format(new Date(timeInMillis)));
buf.append(" ");
return this;
}
/**
* Add a Map to the buffer. Represents maps as a series of (key, value) in quotes.
* The method assumes that both keys and values toString method returns a "nice" string representation.
* The method escapes all quotes
* @param key
* @param map
* @return
*/
public EventLogMessage addMap (String key, Map map)
{
buf.append(key);
buf.append("=");
buf.append("\"");
StringBuffer ps = new StringBuffer();
Set<Map.Entry> pairs = map.entrySet();
for (Map.Entry x : pairs) {
ps.append("(");
ps.append(x.getKey().toString());
if(x.getValue() != null) {
ps.append(",");
ps.append(x.getValue().toString());
}
ps.append(")");
}
buf.append(escape.escape(ps.toString()));
buf.append("\"");
buf.append(" ");
return this;
}
public EventLogMessage addList (String key, List list)
{
buf.append(key);
buf.append("=");
buf.append("\"");
int size = list.size();
for (int i = 0; i < size; i++) {
buf.append(escape.escape(list.get(i).toString()));
if (i != size-1) {
buf.append(",");
}
}
buf.append("\"");
buf.append(" ");
return this;
}
/**
* Add an natural language message to the log message.
* The string is put in quotes and is escaped
* @param msg
* @return
*/
public EventLogMessage addMsg (String msg)
{
buf.append(LoggingKeys.MSG);
buf.append("=");
buf.append("\"");
buf.append(escape.escape(msg));
buf.append("\"");
buf.append(" ");
return this;
}
/**
* Set the timestamp from milliseconds
* returned by System.currentTimeMillis().
*
* @return 'this' so we can chain
*/
public EventLogMessage setTimeStampMillis(long millis)
{
micro2 = millis * 1000;
return this;
}
/**
* Set the timestamp from nanoseconds
* returned by System.nanoTime().
*
* @return 'this' so we can chain
*/
public EventLogMessage 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 EventLogMessage) {
return NEUTRAL; // let message pass through
}
else {
return DENY; // ignore all non-netlogger messages
}
}
}
}