001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.global;
006:
007: import org.geotools.io.LineWriter;
008: import org.geotools.resources.Utilities;
009: import java.io.IOException;
010: import java.io.PrintWriter;
011: import java.io.StringWriter;
012: import java.io.UnsupportedEncodingException;
013: import java.util.logging.ConsoleHandler;
014: import java.util.logging.Formatter;
015: import java.util.logging.Handler;
016: import java.util.logging.Level;
017: import java.util.logging.LogRecord;
018: import java.util.logging.Logger;
019: import java.util.logging.SimpleFormatter;
020: import java.util.logging.StreamHandler;
021: import java.util.prefs.Preferences;
022:
023: /**
024: * <code>Log4JFormatter</code> looks like:
025: * <blockquote>
026: * <pre>
027: * [core FINE] A log message logged with level FINE from the "org.geotools.core"
028: * logger.</pre>
029: * </blockquote>
030: * A formatter writting log message on a single line. This formatter is used by
031: * GeoServer instead of {@link SimpleFormatter}. The main difference is that
032: * this formatter use only one line per message instead of two. For example, a
033: * message formatted by
034: *
035: * @author Martin Desruisseaux
036: * @author Rob Hranac
037: * @version $Id: Log4JFormatter.java,v 1.3 2002/08/19 18:15:30 desruisseaux Exp
038: */
039: public class Log4JFormatter extends Formatter {
040: /**
041: * The string to write at the begining of all log headers (e.g. "[FINE
042: * core]")
043: */
044: private static final String PREFIX = "[";
045:
046: /**
047: * The string to write at the end of every log header (e.g. "[FINE core]").
048: * It should includes the spaces between the header and the message body.
049: */
050: private static final String SUFFIX = "] ";
051:
052: /**
053: * The string to write at the end of every log header (e.g. "[FINE core]").
054: * It should includes the spaces between the header and the message body.
055: */
056: private static long startMillis;
057:
058: /**
059: * The line separator. This is the value of the "line.separator" property
060: * at the time the <code>Log4JFormatter</code> was created.
061: */
062: private final String lineSeparator = System.getProperty(
063: "line.separator", "\n");
064:
065: /**
066: * The line separator for the message body. This line always begin with
067: * {@link #lineSeparator}, followed by some amount of spaces in order to
068: * align the message.
069: */
070: private String bodyLineSeparator = lineSeparator;
071:
072: /**
073: * The minimum amount of spaces to use for writting level and module name
074: * before the message. For example if this value is 12, then a message
075: * from module "org.geotools.core" with level FINE would be formatted as
076: * "<code>[core FINE]</code><cite>the message</cite>" (i.e. the
077: * whole <code>[ ]</code> part is 12 characters wide).
078: */
079: private final int margin;
080:
081: /**
082: * The base logger name. This is used for shortening the logger name when
083: * formatting message. For example, if the base logger name is
084: * "org.geotools" and a log record come from the "org.geotools.core"
085: * logger, it will be formatted as "[LEVEL core]" (i.e. the
086: * "org.geotools" part is ommited).
087: */
088: private final String base;
089:
090: /**
091: * Buffer for formatting messages. We will reuse this buffer in order to
092: * reduce memory allocations.
093: */
094: private final StringBuffer buffer;
095:
096: /**
097: * The line writer. This object transform all "\r", "\n" or "\r\n"
098: * occurences into a single line separator. This line separator will
099: * include space for the marging, if needed.
100: */
101: private final LineWriter writer;
102:
103: /**
104: * Construct a <code>Log4JFormatter</code>.
105: *
106: * @param base The base logger name. This is used for shortening the logger
107: * name when formatting message. For example, if the base logger
108: * name is "org.geotools" and a log record come from the
109: * "org.geotools.core" logger, it will be formatted as "[LEVEL
110: * core]" (i.e. the "org.geotools" part is ommited).
111: */
112: public Log4JFormatter(final String base) {
113: this .base = base.trim();
114: this .margin = getHeaderWidth();
115: Log4JFormatter.startMillis = System.currentTimeMillis();
116:
117: final StringWriter str = new StringWriter();
118: writer = new LineWriter(str);
119: buffer = str.getBuffer();
120: }
121:
122: /**
123: * Format the given log record and return the formatted string.
124: *
125: * @param record the log record to be formatted.
126: *
127: * @return a formatted log record
128: *
129: * @throws AssertionError Should never occur.
130: */
131: public synchronized String format(final LogRecord record) {
132: String logger = record.getLoggerName();
133:
134: final String recordLevel = record.getLevel().getLocalizedName();
135:
136: try {
137: buffer.setLength(1);
138:
139: final Long millis = new Long(record.getMillis()
140: - startMillis);
141: writer.write(millis.toString());
142: writer.write(" ");
143: writer.write(PREFIX);
144: writer.write(recordLevel);
145: writer.write(SUFFIX);
146:
147: if (record.getSourceClassName() != null) {
148: writer.write(record.getSourceClassName());
149: }
150:
151: writer.write(" - ");
152:
153: /*
154: * Now format the message. We will use a line separator made of
155: * the usual EOL ("\r", "\n", or "\r\n", which is plateform
156: * specific) following by some amout of space in order to align
157: * message body.
158: */
159: writer.setLineSeparator(bodyLineSeparator);
160:
161: if (record.getMessage() == null) {
162: record.setMessage("null");
163: }
164:
165: writer.write(formatMessage(record));
166: writer.setLineSeparator(lineSeparator);
167: writer.write('\n');
168:
169: if (record.getThrown() != null) {
170: try {
171: writer.write(getStackTrace(record.getThrown()));
172: } catch (Exception e) {
173: // do not write the exception...
174: }
175: }
176:
177: writer.flush();
178: } catch (IOException exception) {
179: // Should never happen, since we are writting into a StringBuffer.
180: throw new AssertionError(exception);
181: }
182:
183: return buffer.toString();
184: }
185:
186: /**
187: * Returns the full stack trace of the given exception
188: * @param record
189: * @return
190: */
191: private String getStackTrace(Throwable t) {
192: StringWriter sw = new StringWriter();
193: PrintWriter pw = new PrintWriter(sw);
194: t.printStackTrace(pw);
195: pw.close();
196:
197: return sw.toString();
198: }
199:
200: /**
201: * Setup a <code>Log4JFormatter</code> for the specified logger and its
202: * children. This method search for all instances of {@link
203: * ConsoleHandler} using the {@link SimpleFormatter}. If such instances
204: * are found, they are replaced by a single instance of
205: * <code>Log4JFormatter</code> writting to the {@linkPlain System#out
206: * standard output stream} (instead of the {@linkPlain System#err
207: * standard error stream}). This action has no effect on any loggers
208: * outside the <code>base</code> namespace.
209: *
210: * @param base The base logger name to apply the change on (e.g.
211: * "org.geotools").
212: * @param filterLevel The level to log at - overrides user prefs.
213: */
214: public static void init(final String base, Level filterLevel) {
215: Formatter log4j = null;
216:
217: final Logger logger = org.geotools.util.logging.Logging
218: .getLogger(base);
219:
220: //This little routine may be a bit buggy, but it's the best I've got
221: //to make the levels change as we reload the dto's. Feel free to
222: //improve. ch
223: if (!logger.getUseParentHandlers()) {
224: logger.setLevel(filterLevel);
225:
226: if (logger.getHandlers().length > 0) {
227: Handler handler = logger.getHandlers()[0];
228:
229: //this should be the right handler, if set with geoserver.
230: if (handler != null) {
231: handler.setLevel(filterLevel);
232: }
233: }
234: }
235:
236: for (Logger parent = logger; parent.getUseParentHandlers();) {
237: parent = parent.getParent();
238:
239: if (parent == null) {
240: break;
241: }
242:
243: final Handler[] handlers = parent.getHandlers();
244:
245: if (handlers != null) {
246: for (int i = 0; i < handlers.length; i++) {
247: /*
248: * Search for a ConsoleHandler. Search is performed in the target
249: * handler and all its parent loggers. When a ConsoleHandler is
250: * found, it will be replaced by the Stdout handler for 'logger'
251: * only.
252: */
253: Handler handler = handlers[i];
254:
255: if (handler.getClass().equals(ConsoleHandler.class)) {
256: final Formatter formatter = handler
257: .getFormatter();
258:
259: if (formatter.getClass().equals(
260: SimpleFormatter.class)) {
261: if (log4j == null) {
262: log4j = new Log4JFormatter(base);
263: }
264:
265: try {
266: logger.removeHandler(handler);
267: handler = new Stdout(handler, log4j);
268: handler.setLevel(filterLevel);
269: } catch (UnsupportedEncodingException exception) {
270: unexpectedException(exception);
271: } catch (SecurityException exception) {
272: unexpectedException(exception);
273: }
274: }
275: }
276:
277: if (handler.getClass().equals(Stdout.class)) {
278: handler.setLevel(filterLevel);
279: }
280:
281: logger.addHandler(handler);
282: logger.setLevel(filterLevel);
283: }
284: }
285: }
286:
287: //Artie Konin suggested fix (see GEOS-366)
288: if (0 == logger.getHandlers().length) // seems that getHandlers() cannot return null
289: {
290: log4j = new Log4JFormatter(base);
291:
292: Handler handler = new Stdout();
293: handler.setFormatter(log4j);
294: handler.setLevel(filterLevel);
295:
296: logger.addHandler(handler);
297: }
298:
299: logger.setUseParentHandlers(false);
300: }
301:
302: /**
303: * Invoked when an error occurs during the initialization.
304: *
305: * @param e the error that occured.
306: */
307: private static void unexpectedException(final Exception e) {
308: Utilities.unexpectedException("org.geotools.resources",
309: "GeotoolsHandler", "init", e);
310: }
311:
312: /**
313: * Returns the header width. This is the default value to use for {@link
314: * #margin}, if no value has been explicitely set. This value can be set
315: * in user's preferences.
316: *
317: * @return The header width.
318: */
319: private static int getHeaderWidth() {
320: return Preferences.userNodeForPackage(Log4JFormatter.class)
321: .getInt("logging.header", 15);
322: }
323:
324: /**
325: * Set the header width. This is the default value to use for {@link
326: * #margin} for next {@link Log4JFormatter} to be created.
327: *
328: * @param margin the size of the margin to set.
329: */
330: static void setHeaderWidth(final int margin) {
331: Preferences.userNodeForPackage(Log4JFormatter.class).putInt(
332: "logging.header", margin);
333: }
334:
335: /**
336: * A {@link ConsoleHandler} sending output to {@link System#out} instead of
337: * {@link System#err} This handler will use a {@link Log4JFormatter}
338: * writting log message on a single line.
339: *
340: * @task TODO: This class should subclass {@link ConsoleHandler}.
341: * Unfortunatly, this is currently not possible because {@link
342: * ConsoleHandler#setOutputStream} close {@link System#err}. If this
343: * bug get fixed, then {@link #close} no longer need to be
344: * overriden.
345: */
346: private static final class Stdout extends StreamHandler {
347: public Stdout() {
348: super ();
349: }
350:
351: /**
352: * Construct a handler.
353: *
354: * @param handler The handler to copy properties from.
355: * @param formatter The formatter to use.
356: *
357: * @throws UnsupportedEncodingException if the encoding is not valid.
358: */
359: public Stdout(final Handler handler, final Formatter formatter)
360: throws UnsupportedEncodingException {
361: super (System.out, formatter);
362: setErrorManager(handler.getErrorManager());
363: setFilter(handler.getFilter());
364: setLevel(handler.getLevel());
365: setEncoding(handler.getEncoding());
366: }
367:
368: /**
369: * Publish a {@link LogRecord} and flush the stream.
370: *
371: * @param record the log record to publish.
372: */
373: public void publish(final LogRecord record) {
374: super .publish(record);
375: flush();
376: }
377:
378: /**
379: * Override {@link StreamHandler#close} to do a flush but not to close
380: * the output stream. That is, we do <b>not</b> close {@link
381: * System#out}.
382: */
383: public void close() {
384: flush();
385: }
386: }
387: }
|