org.vfny.geoserver.global.Log4JFormatter Maven / Gradle / Ivy
The newest version!
/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.global;
import org.geotools.io.LineWriter;
import org.geotools.resources.Utilities;
import org.geotools.util.logging.Logging;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
import java.util.prefs.Preferences;
/**
* Log4JFormatter
looks like:
*
*
* [core FINE] A log message logged with level FINE from the "org.geotools.core"
* logger.
*
* A formatter writting log message on a single line. This formatter is used by
* GeoServer instead of {@link SimpleFormatter}. The main difference is that
* this formatter use only one line per message instead of two. For example, a
* message formatted by
*
* @author Martin Desruisseaux
* @author Rob Hranac
* @version $Id: Log4JFormatter.java,v 1.3 2002/08/19 18:15:30 desruisseaux Exp
*/
public class Log4JFormatter extends Formatter {
/**
* The string to write at the begining of all log headers (e.g. "[FINE
* core]")
*/
private static final String PREFIX = "[";
/**
* The string to write at the end of every log header (e.g. "[FINE core]").
* It should includes the spaces between the header and the message body.
*/
private static final String SUFFIX = "] ";
/**
* The string to write at the end of every log header (e.g. "[FINE core]").
* It should includes the spaces between the header and the message body.
*/
private static long startMillis;
/**
* The line separator. This is the value of the "line.separator" property
* at the time the Log4JFormatter
was created.
*/
private final String lineSeparator = System.getProperty("line.separator", "\n");
/**
* The line separator for the message body. This line always begin with
* {@link #lineSeparator}, followed by some amount of spaces in order to
* align the message.
*/
private String bodyLineSeparator = lineSeparator;
/**
* The minimum amount of spaces to use for writting level and module name
* before the message. For example if this value is 12, then a message
* from module "org.geotools.core" with level FINE would be formatted as
* "[core FINE]
the message" (i.e. the
* whole [ ]
part is 12 characters wide).
*/
private final int margin;
/**
* The base logger name. This is used for shortening the logger name when
* formatting message. For example, if the base logger name is
* "org.geotools" and a log record come from the "org.geotools.core"
* logger, it will be formatted as "[LEVEL core]" (i.e. the
* "org.geotools" part is ommited).
*/
private final String base;
/**
* Buffer for formatting messages. We will reuse this buffer in order to
* reduce memory allocations.
*/
private final StringBuffer buffer;
/**
* The line writer. This object transform all "\r", "\n" or "\r\n"
* occurences into a single line separator. This line separator will
* include space for the marging, if needed.
*/
private final LineWriter writer;
/**
* Construct a Log4JFormatter
.
*
* @param base The base logger name. This is used for shortening the logger
* name when formatting message. For example, if the base logger
* name is "org.geotools" and a log record come from the
* "org.geotools.core" logger, it will be formatted as "[LEVEL
* core]" (i.e. the "org.geotools" part is ommited).
*/
public Log4JFormatter(final String base) {
this.base = base.trim();
this.margin = getHeaderWidth();
Log4JFormatter.startMillis = System.currentTimeMillis();
final StringWriter str = new StringWriter();
writer = new LineWriter(str);
buffer = str.getBuffer();
}
/**
* Format the given log record and return the formatted string.
*
* @param record the log record to be formatted.
*
* @return a formatted log record
*
* @throws AssertionError Should never occur.
*/
public synchronized String format(final LogRecord record) {
String logger = record.getLoggerName();
final String recordLevel = record.getLevel().getLocalizedName();
try {
buffer.setLength(1);
final Long millis = new Long(record.getMillis() - startMillis);
writer.write(millis.toString());
writer.write(" ");
writer.write(PREFIX);
writer.write(recordLevel);
writer.write(SUFFIX);
if (record.getSourceClassName() != null) {
writer.write(record.getSourceClassName());
}
writer.write(" - ");
/*
* Now format the message. We will use a line separator made of
* the usual EOL ("\r", "\n", or "\r\n", which is plateform
* specific) following by some amout of space in order to align
* message body.
*/
writer.setLineSeparator(bodyLineSeparator);
if (record.getMessage() == null) {
record.setMessage("null");
}
writer.write(formatMessage(record));
writer.setLineSeparator(lineSeparator);
writer.write('\n');
if (record.getThrown() != null) {
try {
writer.write(getStackTrace(record.getThrown()));
} catch (Exception e) {
// do not write the exception...
}
}
writer.flush();
} catch (IOException exception) {
// Should never happen, since we are writting into a StringBuffer.
throw new AssertionError(exception);
}
return buffer.toString();
}
/**
* Returns the full stack trace of the given exception
* @param record
* @return
*/
private String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
return sw.toString();
}
/**
* Setup a Log4JFormatter
for the specified logger and its
* children. This method search for all instances of {@link
* ConsoleHandler} using the {@link SimpleFormatter}. If such instances
* are found, they are replaced by a single instance of
* Log4JFormatter
writting to the {@linkPlain System#out
* standard output stream} (instead of the {@linkPlain System#err
* standard error stream}). This action has no effect on any loggers
* outside the base
namespace.
*
* @param base The base logger name to apply the change on (e.g.
* "org.geotools").
* @param filterLevel The level to log at - overrides user prefs.
*/
public static void init(final String base, Level filterLevel) {
Formatter log4j = null;
final Logger logger = org.geotools.util.logging.Logging.getLogger(base);
//This little routine may be a bit buggy, but it's the best I've got
//to make the levels change as we reload the dto's. Feel free to
//improve. ch
if (!logger.getUseParentHandlers()) {
logger.setLevel(filterLevel);
if (logger.getHandlers().length > 0) {
Handler handler = logger.getHandlers()[0];
//this should be the right handler, if set with geoserver.
if (handler != null) {
handler.setLevel(filterLevel);
}
}
}
for (Logger parent = logger; parent.getUseParentHandlers();) {
parent = parent.getParent();
if (parent == null) {
break;
}
final Handler[] handlers = parent.getHandlers();
if (handlers != null) {
for (int i = 0; i < handlers.length; i++) {
/*
* Search for a ConsoleHandler. Search is performed in the target
* handler and all its parent loggers. When a ConsoleHandler is
* found, it will be replaced by the Stdout handler for 'logger'
* only.
*/
Handler handler = handlers[i];
if (handler.getClass().equals(ConsoleHandler.class)) {
final Formatter formatter = handler.getFormatter();
if (formatter.getClass().equals(SimpleFormatter.class)) {
if (log4j == null) {
log4j = new Log4JFormatter(base);
}
try {
logger.removeHandler(handler);
handler = new Stdout(handler, log4j);
handler.setLevel(filterLevel);
} catch (UnsupportedEncodingException exception) {
unexpectedException(exception);
} catch (SecurityException exception) {
unexpectedException(exception);
}
}
}
if (handler.getClass().equals(Stdout.class)) {
handler.setLevel(filterLevel);
}
logger.addHandler(handler);
logger.setLevel(filterLevel);
}
}
}
//Artie Konin suggested fix (see GEOS-366)
if (0 == logger.getHandlers().length) // seems that getHandlers() cannot return null
{
log4j = new Log4JFormatter(base);
Handler handler = new Stdout();
handler.setFormatter(log4j);
handler.setLevel(filterLevel);
logger.addHandler(handler);
}
logger.setUseParentHandlers(false);
}
/**
* Invoked when an error occurs during the initialization.
*
* @param e the error that occured.
*/
private static void unexpectedException(final Exception e) {
Logging.unexpectedException("org.geotools.resources", Log4JFormatter.class, "init", e);
}
/**
* Returns the header width. This is the default value to use for {@link
* #margin}, if no value has been explicitely set. This value can be set
* in user's preferences.
*
* @return The header width.
*/
private static int getHeaderWidth() {
return Preferences.userNodeForPackage(Log4JFormatter.class).getInt("logging.header", 15);
}
/**
* Set the header width. This is the default value to use for {@link
* #margin} for next {@link Log4JFormatter} to be created.
*
* @param margin the size of the margin to set.
*/
static void setHeaderWidth(final int margin) {
Preferences.userNodeForPackage(Log4JFormatter.class).putInt("logging.header", margin);
}
/**
* A {@link ConsoleHandler} sending output to {@link System#out} instead of
* {@link System#err} This handler will use a {@link Log4JFormatter}
* writting log message on a single line.
*
* @task TODO: This class should subclass {@link ConsoleHandler}.
* Unfortunatly, this is currently not possible because {@link
* ConsoleHandler#setOutputStream} close {@link System#err}. If this
* bug get fixed, then {@link #close} no longer need to be
* overriden.
*/
private static final class Stdout extends StreamHandler {
public Stdout() {
super();
}
/**
* Construct a handler.
*
* @param handler The handler to copy properties from.
* @param formatter The formatter to use.
*
* @throws UnsupportedEncodingException if the encoding is not valid.
*/
public Stdout(final Handler handler, final Formatter formatter)
throws UnsupportedEncodingException {
super(System.out, formatter);
setErrorManager(handler.getErrorManager());
setFilter(handler.getFilter());
setLevel(handler.getLevel());
setEncoding(handler.getEncoding());
}
/**
* Publish a {@link LogRecord} and flush the stream.
*
* @param record the log record to publish.
*/
public void publish(final LogRecord record) {
super.publish(record);
flush();
}
/**
* Override {@link StreamHandler#close} to do a flush but not to close
* the output stream. That is, we do not close {@link
* System#out}.
*/
public void close() {
flush();
}
}
}