[ start | index | login ]
start > Virtual Brain > Java > Debug-Appender

Debug-Appender

Created by stefan. Last edited by stefan, one year and 109 days ago. Viewed 532 times. #9
[diff] [history] [edit] [rdf]
labels

Log4j Appender that supports a DEBUG dump on ERRORs

Logfiles with too many debug statements are too noisy and not useful in production - sometimes they also grow too big. However, many developers wish to have some more debugging information if an error occured.

The idea to implement this requirement with a ringbuffer that stores the last N log statements is not new, but I haven't found a free implementation.

Here it is :-)

Download and Source

Find all the source in the attached ZIP file. If you are only looking for the binaries, you can use the attached JAR file.

Configuration

First a quick look on how to configure this in the log4j.xml file. This is an example configuration file for the DebugSavingAppenderAttachable.

It defines a standard appender for the console to show you the log results. This console appender is then added to an appender called "DUMPER", this is the one that will save DEBUG statements and dump them if an ERROR occurs.

Important is the <root> section where the root logger is defined, it must only contain the DUMPER appender.

<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out"/>
    <param name="Threshold" value="DEBUG"/>

<layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/> </layout> </appender>

<appender name="DUMPER" class="ch.stefanrufer.log4jextension.DebugSavingAppenderAttachable"> <param name="Threshold" value="INFO"/> <param name="DebugDumpThreshold" value="ERROR"/> <param name="RingbufferSize" value="10"/> <appender-ref ref="CONSOLE"/> </appender>

<root> <appender-ref ref="DUMPER"/> </root>

Code

Then the implementation of the DebugSavingAppenderAttachable:

/*
 * This is free software. Use it, re-distribute it, change it but don't complain.
 * 
 * Author: Stefan Rufer, >>http://www.stefanrufer.ch
 */
package ch.stefanrufer.log4jextension;

import java.util.Enumeration; import java.util.Iterator;

import org.apache.log4j.Appender; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.Priority; import org.apache.log4j.helpers.AppenderAttachableImpl; import org.apache.log4j.spi.AppenderAttachable; import org.apache.log4j.spi.LoggingEvent;

/** * This combined {@link Appender}/{@link AppenderAttachable} is able to store log messages in a * ring buffer and dump them if a severe log event occurred. A typical use case is to dump the * past few DEBUG messages if an ERROR log is detected. A typical configuration in a log4j.xml * file may look like this: * * <pre> * <code> * <appender name="DUMPER" * class="ch.stefanrufer.log4jextension.DebugSavingAppenderAttachable"> * <param name="Threshold" value="INFO"/> * <param name="DebugDumpThreshold" value="ERROR"/> * <param name="RingbufferSize" value="10"/> * <appender-ref ref="FILE"/> * </appender> * </code> * </pre> * * This means that log messages with {@link Level#INFO} and {@link Level#WARN} will be logged as * usual. A log event with {@link Level#ERROR} or more severe will also be logged as usual but * right after the last 10 messages with level <b>less</b> severe than {@link Level#INFO} will be * dumped to the appender named "FILE". */ public class DebugSavingAppenderAttachable extends AppenderSkeleton implements AppenderAttachable {

/** Default size of the underlying ring buffer used to store non-logged statements. */ private static final int RINGBUFFER_DEFAULT_SIZE = 100;

/** * The delegate object where the {@link AppenderAttachable} operations are implemented. */ private AppenderAttachableImpl delegate = new AppenderAttachableImpl();

/** The level when to start a debug dump. Defaults to {@link Level#ERROR}. */ private Level debugDumpThreshold = Level.ERROR;

/** * The size of the ring buffer used to keep unlogged messages. Defaults to * {@link #RINGBUFFER_DEFAULT_SIZE}. */ private int ringbufferSize = RINGBUFFER_DEFAULT_SIZE;

/** The ring buffer used internally for "saved" log statements. */ private Ringbuffer<LoggingEvent> ringbuffer = null;

private Level threshold = Level.INFO;

/** * Delegates the given log event to all loggers referenced by this {@link AppenderAttachable} if * the log level of the log event is more severe than the configured threshold. If it is less * severe, the log event goes into a ring buffer. If the log level of the log event is the same * or more severe as {@link #debugDumpThreshold} the ring buffer with the formerly stored debug * messages will be dumped and cleared. * * @param event the log event to be checked and maybe logged */ @Override protected void append(LoggingEvent event) { if (threshold.toInt() > event.getLevel().toInt()) { if (ringbuffer == null) { ringbuffer = new Ringbuffer<LoggingEvent>(ringbufferSize); } ringbuffer.add(event); } else { delegate.appendLoopOnAppenders(event); if (debugDumpThreshold.toInt() <= event.getLevel().toInt()) { dumpAndClearRingbuffer(); } } }

/** * This stores the given threshold internally and does not pass this on to the child loggers of * this appender attachable. * * @param threshold the new threshold */ @Override public void setThreshold(Priority threshold) { this.threshold = Level.toLevel(threshold.toString()); }

/** * Dumps the content of the ring buffer (if available) to all appenders referenced by this * {@link AppenderAttachable}. After the dump, the ring buffer is cleared. */ private void dumpAndClearRingbuffer() { if (ringbuffer == null) { return; }

delegate.appendLoopOnAppenders(new LoggingEvent("ch.stefanrufer.log4jextension", Logger.getLogger(this.getClass()), Priority.INFO, "Dumping the last " + ringbuffer.getCount() + " log messages with level " + threshold + " or less important because a log message with level " + debugDumpThreshold + " or more severe was detected. Hope it helps for debugging...", null)); Iterator<LoggingEvent> it = ringbuffer.iterator(); while (it.hasNext()) { delegate.appendLoopOnAppenders(it.next()); } ringbuffer.clear(); }

/** * {@inheritDoc} */ public void close() { Enumeration<Appender> en = delegate.getAllAppenders(); while (en.hasMoreElements()) { en.nextElement().close(); } }

/** * Gets the debugDumpThreshold. * * @return the debugDumpThreshold */ public Level getDebugDumpThreshold() { return debugDumpThreshold; }

/** * Sets the debugDumpThreshold. * * @param debugDumpThreshold the debugDumpThreshold to set */ public void setDebugDumpThreshold(Level debugDumpThreshold) { this.debugDumpThreshold = debugDumpThreshold; }

/** * Gets the ringbufferSize. * * @return the ringbufferSize */ public int getRingbufferSize() { return ringbufferSize; }

/** * Sets the ringbufferSize. * * @param ringbufferSize the ringbufferSize to set */ public void setRingbufferSize(int ringbufferSize) { this.ringbufferSize = ringbufferSize; }

/** * {@inheritDoc} */ public void addAppender(Appender newAppender) { delegate.addAppender(newAppender); }

/** * {@inheritDoc} */ public Enumeration<Appender> getAllAppenders() { return delegate.getAllAppenders(); }

/** * {@inheritDoc} */ public Appender getAppender(String name) { return delegate.getAppender(name); }

/** * {@inheritDoc} */ public boolean isAttached(Appender appender) { return delegate.isAttached(appender); }

/** * {@inheritDoc} */ public void removeAllAppenders() { delegate.removeAllAppenders(); }

/** * {@inheritDoc} */ public void removeAppender(Appender appender) { delegate.removeAppender(appender); }

/** * {@inheritDoc} */ public void removeAppender(String name) { delegate.removeAppender(name); }

/** * {@inheritDoc} */ public boolean requiresLayout() { return false; }

}

And a helper class that is needed:

/*
 * This is free software. Use it, re-distribute it, change it but don't complain.
 * 
 * Author: Stefan Rufer, >>http://www.stefanrufer.ch
 */
package ch.stefanrufer.log4jextension;

import java.util.ArrayList; import java.util.Iterator;

/** * Simple ring buffer implementation that can store a fixed amount of elements in an internal * buffer. If the maximum size is reached, the element added first will be overwritten. * No elements can be removed from the ring buffer. * <p> * This implementation is not thread safe. */ public class Ringbuffer<T> {

private ArrayList<T> buffer = null;

private int last = -1; private int size = 0; private boolean overflow = false;

/** * Create a new ring buffer with the given size. The size can not be changed later on and is * fixed. * * @param size fixed size of the ring buffer * @throws IllegalArgumentException if size is < 1 */ public Ringbuffer(int size) { if (size < 1) { throw new IllegalArgumentException("Ringbuffer with size < 1 can not be created"); }

this.size = size; buffer = new ArrayList<T>(size); }

/** * Add the given element to the ring buffer. It will be appended at the end of the internal * buffer or replace the oldest element if the maximum capacity was reached. * * @param element the element to add */ public void add(T element) { last++; last = last % size; if (buffer.size() < size) { buffer.add(element); } else { // we are overwriting elements overflow = true; buffer.set(last, element); } }

/** * Clear the ring buffer. All elements currently in the ring buffer will be removed. */ public void clear() { last = -1; overflow = false; buffer.clear(); }

/** * Get all the elements that are currently stored in the ring buffer. The order * of the added elements is kept and the resulting list will contain the "oldest" * element first in list and the last one added as last in list. * The returned list will have a size equal to {@link #getCount()}. * * @return all the elements in the ring buffer in correct order */ public ArrayList<T> elements() { ArrayList<T> result = new ArrayList<T>(size);

// maybe there would be an elegant solution to combine these two branches, but // this works and is easy to understand if (overflow) { // we had an overflow => start the elements list one after last for (int i = last + 1; i < last + 1 + size; i++) { result.add(buffer.get(i % size)); } } else { // without overflow, just add all the elements in the buffer to the result for (int i = 0; i < buffer.size(); i++) { result.add(buffer.get(i)); } }

return result; }

/** * Get an iterator of all the elements that are currently stored in the ring buffer. The order * of the added elements is kept and the resulting iterator will first present the oldest * element of the ring buffer. * * @return Iterator for all the elements in the ring buffer in correct order */ public Iterator<T> iterator() { return elements().iterator(); }

/** * Get the current count of elements in the ring buffer. The minimum * count returned is 0 the maximum is the size of the ring buffer. * * @return count of elements in the ring buffer */ public int getCount() { return buffer.size(); }

/** * Get the size of the ring buffer. * * @return the size */ public int getSize() { return size; } }

History

  • 2007-10-08: thanks to Joachim Hagger for pointing out a bug in the initialization of the Ringbuffer (added size validation that was documented in JavaDoc but missing in the code)
  • 2011-02-02: thanks to Roland Weiss for pointing out a bug in the Ringbuffer (clear method needs to set overflow to false)
no comments | post comment
search www.stefanrufer.ch
Google

Content

Me?


Blog Calendar

< May 2012 >
SunMonTueWedThuFriSat
12345
6789101112
13141516171819
20212223242526
2728293031

Weblog summary 2007, 2006, 2005, 2004


Content managed by SnipSnap

M

snipsnap.org | Copyright 2000-2002 Matthias L. Jugel and Stephan J. Schmidt