/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.jcoderz.commons.logging.LogLineFormat;
import org.jcoderz.commons.logging.LogLineFormatFactory;
/**
* This type implements a Formatter to be used for logging in a format, which
* allows filtering of log files with standard tools and little effort. It
* formats both standard a {@link java.util.logging.LogRecord} and instances of
* {@link org.jcoderz.commons.Loggable}.
*
*/
public class LogFormatter
extends Formatter
{
/**
* Name of the logger that controls which log level is needed as minimum
* to trigger stack traces with log messages.
*/
public static final String MSG_LOGGER_STACK_TRACE = "msgLoggerStackTrace";
private static final Logger FWK_TRACE_LOGGER_LOGGER
= Logger.getLogger(MSG_LOGGER_STACK_TRACE);
private final ThreadLocal mMessageFormatters = new ThreadLocal();
/** {@inheritDoc} */
public String format (LogRecord record)
{
final StringBuffer sb = new StringBuffer();
Loggable loggable = null;
if (record.getParameters() != null && record.getParameters().length > 0)
{
if (record.getParameters()[0] instanceof Loggable)
{
loggable = (Loggable) record.getParameters()[0];
}
}
format(sb, record, loggable);
return sb.toString();
}
/**
* Gets the message format for a log line with <code>type</code> as type
* specifier.
*
* @param type The type specifier for the requested message format.
*
* @return MessageFormat for log line of type <code>type</code>
*/
private LogLineFormat getMessageFormat (
final LogLineFormat.LogLineType type)
{
Map formatters = (Map) mMessageFormatters.get();
if (formatters == null)
{
formatters = createMessageFormats();
mMessageFormatters.set(formatters);
}
return (LogLineFormat) formatters.get(type);
}
private Map createMessageFormats ()
{
final Map rc = new HashMap();
addMessageFormat(rc, LogLineFormat.TRACE_MESSAGE);
addMessageFormat(rc, LogLineFormat.EXCEPTION_MESSAGE);
addMessageFormat(rc, LogLineFormat.LOG_MESSAGE);
addMessageFormat(rc, LogLineFormat.ERROR_MESSAGE);
addMessageFormat(rc, LogLineFormat.STACKTRACE_MESSAGE);
addMessageFormat(rc, LogLineFormat.PARAMETER_LINE);
addMessageFormat(rc, LogLineFormat.NESTED_MESSAGE);
return rc;
}
/**
* Formats a LogRecord, which does not carry any parameters. In this case it
* is a trace record, not a Loggable is logged.
*
* @param sb the StringBuffer where to append the formatted log record
* @param record the log record to format
* @param trackingIdSequence a list collecting all tracking ids of messages
* being formatted by one call.
*/
private void formatLogRecord (
final StringBuffer sb,
final LogRecord record,
final List trackingIdSequence)
{
LogLineFormat.LogLineType type;
if (record.getThrown() != null)
{
type = LogLineFormat.EXCEPTION_MESSAGE;
}
else
{
type = LogLineFormat.TRACE_MESSAGE;
}
final LogLineFormat format = getMessageFormat(type);
format.format(sb, record, null, trackingIdSequence, null, null);
}
/**
* Appends a full stack trace carried by the supplied LogRecord or Loggable
* to the string buffer. If neither of them carries a Throwable, nothing is
* done here. The stack trace appended by this contains the complete chain
* of throwables.
*
* @param sb the StringBuffer to which to append the stack trace
* @param record The LogRecord
* @param loggable the Loggable, might be null.
* @param trackingIdSequence the list collecting the sequence of tracking
* ids, must not be null.
*/
private void appendStackTrace (
final StringBuffer sb,
final LogRecord record,
final Loggable loggable,
final List trackingIdSequence)
{
Throwable thrown = getTopLevelThrown(record, loggable);
Throwable outerTrace = null;
final LogLineFormat.LogLineType type = LogLineFormat.STACKTRACE_MESSAGE;
final LogLineFormat format = getMessageFormat(type);
while (thrown != null)
{
if (thrown instanceof Loggable)
{
addTrackingNumber(trackingIdSequence, (Loggable) thrown);
}
format.format(sb, record, loggable, trackingIdSequence,
thrown, outerTrace);
outerTrace = thrown;
thrown = outerTrace.getCause();
}
}
/**
* Appends the parameters carried by the supplied LogRecord or Loggable
* to the string buffer. If there are no parameters, nothing is done here.
*
* @param sb the StringBuffer to which to append the stack trace
* @param loggable the Loggable, might be null.
* @param trackingIdSequence the list collecting the sequence of tracking
* ids, must not be null.
*/
private void appendParameters (
final StringBuffer sb,
final LogRecord record,
final Loggable loggable,
final List trackingIdSequence)
{
final LogLineFormat.LogLineType type = LogLineFormat.PARAMETER_LINE;
final LogLineFormat format = getMessageFormat(type);
format.format(sb, record, loggable, trackingIdSequence, null, null);
}
/**
* This loops through the nested Loggables/throwables and formats the
* complete message stack.
*
* @param sb The StringBuffer where to append the formatted message stack.
* @param record The source LogRecord to format
* @param loggable The first instance of Loggable, might be null if
* <code>record</code> does not carry a Loggable.
*/
private void format (
final StringBuffer sb,
final LogRecord record,
final Loggable loggable)
{
List trackingIds = initialiseTrackingIds(record, loggable);
Loggable currentLoggable = loggable;
boolean isFirst = true;
Throwable cause = null;
while (isFirst || (! ((currentLoggable == null) && (cause == null))))
{
Throwable nestedCause = null;
if (currentLoggable != null)
{
formatLoggable(sb, record, currentLoggable, trackingIds);
nestedCause = currentLoggable.getCause();
}
else if (isFirst)
{
formatLogRecord(sb, record, trackingIds);
nestedCause = record.getThrown();
}
isFirst = false;
cause = (cause != null) ? cause.getCause() : nestedCause;
currentLoggable = null;
if (cause != null)
{
appendNestingLevel(sb, record, cause, trackingIds);
if (cause instanceof Loggable)
{
currentLoggable = (Loggable) cause;
}
}
}
// for messages: do not log stack traces for log messages of level
// below the FWK_TRACE_LOGGER_LOGGER log level.
if (!(loggable instanceof LogEvent)
|| FWK_TRACE_LOGGER_LOGGER.isLoggable(record.getLevel()))
{
trackingIds = initialiseTrackingIds(record, loggable);
appendStackTrace(sb, record, loggable, trackingIds);
}
}
private void formatLoggable (
final StringBuffer sb,
final LogRecord record,
final Loggable loggable,
final List trackingIds)
{
final LogLineFormat.LogLineType type = determineType(loggable);
final LogLineFormat format = getMessageFormat(type);
format.format(sb, record, loggable, trackingIds, null, null);
appendParameters(sb, record, loggable, trackingIds);
}
/**
* Creates the message format for the specified type and adds it to the
* supplied map.
*
* @param msgFormats The map to which to add the new message format with
* <code>type</code> as key.
* @param type The type for which to create the format and add to the map.
*/
private void addMessageFormat (
final Map msgFormats,
final LogLineFormat.LogLineType type)
{
final LogLineFormat format = LogLineFormatFactory.create(type);
msgFormats.put(type, format);
}
/**
* Determines the log line type for the supplied Loggable.
*
* @param loggable The Loggable for which to determine the logline type.
*
* @return The correct LogLineType for <code>loggable</code>.
*
* @see LogLineType
*/
private LogLineFormat.LogLineType determineType (final Loggable loggable)
{
final Throwable cause;
final LogLineFormat.LogLineType rc;
if (loggable instanceof Throwable)
{
cause = (Throwable) loggable;
}
else
{
cause = loggable.getCause();
}
if ((cause != null) && ! (cause instanceof LogEvent))
{
rc = LogLineFormat.ERROR_MESSAGE;
}
else
{
rc = LogLineFormat.LOG_MESSAGE;
}
return rc;
}
/**
* Appends a nesting level to the StringBuffer. This is performed if the
* current Loggable carries a cause, which might be a Loggable itself.
* In case the cause is a Loggable, the tracking id sequence is extended with
* its tracking id and the symbol name is logged here. If the cause is not a
* Loggable, its name and message are logged.
*
* @param sb The StringBuffer where to append the nesting level.
* @param record The LogRecord currently formatted.
* @param cause The Throwable causing the nesting level.
* @param trackingIdSequence The list collecting the sequence of tracking ids.
*/
private void appendNestingLevel (
final StringBuffer sb,
final LogRecord record,
final Throwable cause,
final List trackingIdSequence)
{
final Loggable loggable;
if (cause instanceof Loggable)
{
loggable = (Loggable) cause;
}
else
{
loggable = null;
}
final LogLineFormat.LogLineType type = LogLineFormat.NESTED_MESSAGE;
final LogLineFormat format = getMessageFormat(type);
if (loggable == null)
{
format.format(sb, record, null, trackingIdSequence, null, cause);
}
else
{
addTrackingNumber(trackingIdSequence, loggable);
format.format(sb, record, loggable, trackingIdSequence, null,
loggable.getLogMessageInfo().getSymbol());
}
}
/**
* Initialises the list holding the sequence of tracking ids. Creates a new
* list and fills it with the first tracking id, which is taken from the
* supplied loggable. If this is null, the sequence number of the supplied
* log record is taken.
*
* @param record The log record to format. Must not be null.
* @param loggable The loggable being encapsulated by <code>record</code>,
* might be null.
*
* @return List with first tracking id.
*/
private List initialiseTrackingIds (
final LogRecord record,
final Loggable loggable)
{
final List rc = new ArrayList();
if (loggable != null)
{
addTrackingNumber(rc, loggable);
}
else
{
addTrackingNumber(rc, record);
}
return rc;
}
/**
* Adds the record's sequence number as new tracking id to the sequence of
* tracking ids, if it is not already included as last element.
*
* @param trackingIds The list storing the sequence of tracking ids.
* @param record The record for which to add the sequence number.
*/
private void addTrackingNumber (
final List trackingIds,
final LogRecord record)
{
addTrackingNumber(trackingIds,
Integer.toHexString((int) record.getSequenceNumber()));
}
/**
* Adds the loggable's tracking number as new tracking id to the sequence of
* tracking ids, if it is not already included as last element.
*
* @param trackingIds The list storing the sequence of tracking ids.
* @param loggable The Loggable for which to add the tracking number.
*/
private void addTrackingNumber (
final List trackingIds,
final Loggable loggable)
{
addTrackingNumber(trackingIds, loggable.getTrackingNumber());
}
/**
* Adds the new tracking number as new tracking id to the sequence of
* tracking ids, if it is not already included as last element.
*
* @param trackingIds The list storing the sequence of tracking ids.
* @param newId The number to add to the sequence.
*/
private void addTrackingNumber (
final List trackingIds,
final String newId)
{
if (! trackingIds.isEmpty())
{
if (! trackingIds.get(trackingIds.size() - 1).equals(newId))
{
trackingIds.add(newId);
}
}
else
{
trackingIds.add(newId);
}
}
/**
* Gets the top level throwable from the supplied LogRecord and Loggable.
* This is either the cause of <code>record or loggable</code> or
* <code>loggable</code> itself.
*
* @param record The LogRecord currently formatted.
* @param loggable The Loggable carried by <code>record</code>
*
* @return top level Throwable, might be null if no such.
*/
private Throwable getTopLevelThrown (
final LogRecord record,
final Loggable loggable)
{
final Throwable thrown;
if (loggable == null)
{
thrown = record.getThrown();
}
else
{
if (loggable instanceof Throwable)
{
thrown = (Throwable) loggable;
}
else
{
thrown = loggable.getCause();
}
}
return thrown;
}
}