001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.util;
018:
019: // J2SE dependencies
020: import java.io.IOException;
021: import java.io.StringWriter;
022: import java.io.UnsupportedEncodingException;
023: import java.text.FieldPosition;
024: import java.text.SimpleDateFormat;
025: import java.util.Date;
026: import java.util.TimeZone;
027: import java.util.logging.ConsoleHandler;
028: import java.util.logging.Formatter;
029: import java.util.logging.Handler;
030: import java.util.logging.Level;
031: import java.util.logging.LogManager;
032: import java.util.logging.LogRecord;
033: import java.util.logging.Logger;
034: import java.util.logging.SimpleFormatter;
035: import java.util.logging.StreamHandler;
036:
037: // Geotools dependencies
038: import org.geotools.io.LineWriter;
039: import org.geotools.resources.Utilities;
040:
041: /**
042: * A formatter writting log messages on a single line. This formatter is used by
043: * Geotools 2 instead of {@link SimpleFormatter}. The main difference is that
044: * this formatter use only one line per message instead of two. For example, a
045: * message formatted by {@code MonolineFormatter} looks like:
046: *
047: * <blockquote><pre>
048: * FINE core - A log message logged with level FINE from the "org.geotools.core" logger.
049: * </pre></blockquote>
050: *
051: * By default, {@code MonolineFormatter} display only the level and the
052: * message. Additional fields can be formatted if {@link #setTimeFormat} or
053: * {@link #setSourceFormat} methods are invoked with a non-null argument. The
054: * format can also be set from the {@code jre/lib/logging.properties}
055: * file. For example, user can cut and paste the following properties into
056: * {@code logging.properties}:
057: *
058: * <blockquote><pre>
059: * ############################################################
060: * # Properties for the Geotools's MonolineFormatter.
061: * # By default, the monoline formatter display only the level
062: * # and the message. Additional fields can be specified here:
063: * #
064: * # time: If set, writes the time ellapsed since the initialization.
065: * # The argument specifies the output pattern. For example, the
066: * # pattern HH:mm:ss.SSSS display the hours, minutes, seconds
067: * # and milliseconds.
068: * #
069: * # source: If set, writes the source logger or the source class name.
070: * # The argument specifies the type of source to display. Valid
071: * # values are none, logger:short, logger:long, class:short and
072: * # class:long.
073: * ############################################################
074: * org.geotools.util.MonolineFormatter.time = HH:mm:ss.SSS
075: * org.geotools.util.MonolineFormatter.source = class:short
076: * </pre></blockquote>
077: *
078: * If the {@code MonolineFormatter} is wanted for the whole system
079: * (not just the {@code org.geotools} packages) with level FINE (for
080: * example), then the following properties can be defined as below:
081: *
082: * <blockquote><pre>
083: * java.util.logging.ConsoleHandler.formatter = org.geotools.util.MonolineFormatter
084: * java.util.logging.ConsoleHandler.encoding = Cp850
085: * java.util.logging.ConsoleHandler.level = FINE
086: * </pre></blockquote>
087: *
088: * @since 2.0
089: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/util/MonolineFormatter.java $
090: * @version $Id: MonolineFormatter.java 27848 2007-11-12 13:10:32Z desruisseaux $
091: * @author Martin Desruisseaux
092: *
093: * @deprecated Moved to the {@link org.geotools.util.logging} package.
094: */
095: public class MonolineFormatter extends Formatter {
096: /**
097: * The string to write at the begining of all log headers (e.g. "[FINE core]")
098: */
099: private static final String PREFIX = "";
100:
101: /**
102: * The string to write at the end of every log header (e.g. "[FINE core]").
103: * It should includes the spaces between the header and the message body.
104: */
105: private static final String SUFFIX = " - ";
106:
107: /**
108: * The default header width.
109: */
110: private static final int DEFAULT_WIDTH = 9;
111:
112: /** Do not format source class name. */
113: private static final int NO_SOURCE = 0;
114: /** Explicit value for 'none'. */
115: private static final int NO_SOURCE_EX = 1;
116: /** Format the source logger without base. */
117: private static final int LOGGER_SHORT = 2;
118: /** Format the source logger only. */
119: private static final int LOGGER_LONG = 3;
120: /** Format the class name without package. */
121: private static final int CLASS_SHORT = 4;
122: /** Format the fully qualified class name. */
123: private static final int CLASS_LONG = 5;
124:
125: /**
126: * The label to use in the {@code logging.properties} for setting the source format.
127: */
128: private static String[] FORMAT_LABELS = new String[6];
129: static {
130: FORMAT_LABELS[NO_SOURCE_EX] = "none";
131: FORMAT_LABELS[LOGGER_SHORT] = "logger:short";
132: FORMAT_LABELS[LOGGER_LONG] = "logger:long";
133: FORMAT_LABELS[CLASS_SHORT] = "class:short";
134: FORMAT_LABELS[CLASS_LONG] = "class:long";
135: }
136:
137: /**
138: * The line separator. This is the value of the "line.separator"
139: * property at the time the {@code MonolineFormatter} was created.
140: */
141: private final String lineSeparator = System.getProperty(
142: "line.separator", "\n");
143:
144: /**
145: * The line separator for the message body. This line always begin with
146: * {@link #lineSeparator}, followed by some amount of spaces in order to
147: * align the message.
148: */
149: private String bodyLineSeparator = lineSeparator;
150:
151: /**
152: * The minimum amount of spaces to use for writting level and module name
153: * before the message. For example if this value is 12, then a message from
154: * module "org.geotools.core" with level FINE would be formatted as
155: * "<code>[core FINE]</code> <cite>the message</cite>"
156: * (i.e. the whole <code>[ ]</code> part is 12 characters wide).
157: */
158: private final int margin;
159:
160: /**
161: * The base logger name. This is used for shortening the logger name when
162: * formatting message. For example, if the base logger name is "org.geotools"
163: * and a log record come from the "org.geotools.core" logger, it will be
164: * formatted as "[LEVEL core]" (i.e. the "org.geotools" part is ommited).
165: */
166: private final String base;
167:
168: /**
169: * Time of {@code MonolineFormatter} creation,
170: * in milliseconds ellapsed since January 1, 1970.
171: */
172: private final long startMillis;
173:
174: /**
175: * The format to use for formatting ellapsed time,
176: * or {@code null} if there is none.
177: */
178: private SimpleDateFormat timeFormat = null;
179:
180: /**
181: * One of the following constants: {@link #NO_SOURCE},
182: * {@link #LOGGER_SHORT}, {@link #LOGGER_LONG},
183: * {@link #CLASS_SHORT} or {@link #CLASS_LONG}.
184: */
185: private int sourceFormat = NO_SOURCE;
186:
187: /**
188: * Buffer for formatting messages. We will reuse this
189: * buffer in order to reduce memory allocations.
190: */
191: private final StringBuffer buffer;
192:
193: /**
194: * The line writer. This object transform all "\r", "\n" or "\r\n" occurences
195: * into a single line separator. This line separator will include space for
196: * the marging, if needed.
197: */
198: private final LineWriter writer;
199:
200: /**
201: * Construct a default {@code MonolineFormatter}.
202: */
203: public MonolineFormatter() {
204: this ("");
205: }
206:
207: /**
208: * Construct a {@code MonolineFormatter}.
209: *
210: * @param base The base logger name. This is used for shortening the logger
211: * name when formatting message. For example, if the base
212: * logger name is "org.geotools" and a log record come from
213: * the "org.geotools.core" logger, it will be formatted as
214: * "[LEVEL core]" (i.e. the "org.geotools" part is ommited).
215: */
216: public MonolineFormatter(final String base) {
217: this .startMillis = System.currentTimeMillis();
218: this .margin = DEFAULT_WIDTH;
219: this .base = base.trim();
220: StringWriter str = new StringWriter();
221: writer = new LineWriter(str);
222: buffer = str.getBuffer();
223: buffer.append(PREFIX);
224:
225: // Configure this formatter
226: final LogManager manager = LogManager.getLogManager();
227: final String classname = MonolineFormatter.class.getName();
228: try {
229: setTimeFormat(manager.getProperty(classname + ".time"));
230: } catch (IllegalArgumentException exception) {
231: // Can't use the logging framework, since we are configuring it.
232: // Display the exception name only, not the trace.
233: System.err.println(exception);
234: }
235: try {
236: setSourceFormat(manager.getProperty(classname + ".source"));
237: } catch (IllegalArgumentException exception) {
238: System.err.println(exception);
239: }
240: }
241:
242: /**
243: * Set the format for displaying ellapsed time. The pattern must matches
244: * the format specified in {@link SimpleDateFormat}. For example, the
245: * pattern <code>"HH:mm:ss.SSS"</code> will display the ellapsed time
246: * in hours, minutes, seconds and milliseconds.
247: *
248: * @param pattern The time patter, or {@code null} to disable time formatting.
249: */
250: public synchronized void setTimeFormat(final String pattern) {
251: if (pattern == null) {
252: timeFormat = null;
253: } else if (timeFormat == null) {
254: timeFormat = new SimpleDateFormat(pattern);
255: timeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
256: } else {
257: timeFormat.applyPattern(pattern);
258: }
259: }
260:
261: /**
262: * Returns the format for displaying ellapsed time. This is the pattern specified
263: * to the last call to {@link #setTimeFormat}, or the patten specified in the
264: * {@code org.geotools.MonolineFormater.time} property in the
265: * {@code jre/lib/logging.properties} file.
266: *
267: * @return The time pattern, or {@code null} if time is not formatted.
268: */
269: public synchronized String getTimeFormat() {
270: return (timeFormat != null) ? timeFormat.toPattern() : null;
271: }
272:
273: /**
274: * Set the format for displaying the source. The pattern may be one of the following:
275: *
276: * <code>"none"</code>,
277: * <code>"logger:short"</code>, <code>"class:short"</code>,
278: * <code>"logger:long"</code> or <code>"class:long"</code>.
279: *
280: * The difference between a {@code null} and <code>"none"</code> is that {@code null}
281: * may be replaced by a default value, while <code>"none"</code> means that the user
282: * explicitly requested no source.
283: *
284: * @param format The format for displaying the source.
285: */
286: public synchronized void setSourceFormat(String format) {
287: if (format != null) {
288: format = format.trim().toLowerCase();
289: }
290: for (int i = 0; i < FORMAT_LABELS.length; i++) {
291: if (Utilities.equals(FORMAT_LABELS[i], format)) {
292: sourceFormat = i;
293: return;
294: }
295: }
296: throw new IllegalArgumentException(format);
297: }
298:
299: /**
300: * Returns the format for displaying the source. This is the pattern specified
301: * to the last call to {@link #setSourceFormat}, or the patten specified in the
302: * {@code org.geotools.MonolineFormater.source} property in the
303: * {@code jre/lib/logging.properties} file.
304: *
305: * @return The source pattern, or {@code null} if source is not formatted.
306: */
307: public String getSourceFormat() {
308: return FORMAT_LABELS[sourceFormat];
309: }
310:
311: /**
312: * Format the given log record and return the formatted string.
313: *
314: * @param record the log record to be formatted.
315: * @return a formatted log record
316: */
317: public synchronized String format(final LogRecord record) {
318: buffer.setLength(PREFIX.length());
319: /*
320: * Format the time (e.g. "00:00:12.365"). The time pattern can be set
321: * either programmatically with a call to setTimeFormat(String), or in
322: * the logging.properties file with the
323: * "org.geotools.util.MonolineFormatter.time" property.
324: */
325: if (timeFormat != null) {
326: Date time = new Date(Math.max(0, record.getMillis()
327: - startMillis));
328: timeFormat.format(time, buffer, new FieldPosition(0));
329: buffer.append(' ');
330: }
331: /*
332: * Format the level (e.g. "FINE"). We do not provide
333: * the option to turn level off for now.
334: */
335: if (true) {
336: int offset = buffer.length();
337: buffer.append(record.getLevel().getLocalizedName());
338: offset = buffer.length() - offset;
339: buffer.append(Utilities.spaces(margin - offset));
340: }
341: /*
342: * Add the source. It may be either the source logger or the source
343: * class name.
344: */
345: String logger = record.getLoggerName();
346: String classname = record.getSourceClassName();
347: switch (sourceFormat) {
348: case LOGGER_SHORT: {
349: if (logger.startsWith(base)) {
350: int pos = base.length();
351: if (pos < logger.length() - 1
352: && logger.charAt(pos) == '.') {
353: pos++;
354: }
355: logger = logger.substring(pos);
356: }
357: // fall through
358: }
359: case LOGGER_LONG: {
360: buffer.append(' ');
361: buffer.append(logger);
362: break;
363: }
364: case CLASS_SHORT: {
365: int dot = classname.lastIndexOf('.');
366: if (dot >= 0) {
367: classname = classname.substring(dot + 1);
368: }
369: classname = classname.replace('$', '.');
370: // fall through
371: }
372: case CLASS_LONG: {
373: buffer.append(' ');
374: buffer.append(classname);
375: break;
376: }
377: }
378: buffer.append(SUFFIX);
379: /*
380: * Now format the message. We will use a line separator made of the
381: * usual EOL ("\r", "\n", or "\r\n", which is plateform specific)
382: * following by some amout of space in order to align message body.
383: */
384: final int margin = buffer.length();
385: assert margin >= this .margin;
386: if (bodyLineSeparator.length() != lineSeparator.length()
387: + margin) {
388: bodyLineSeparator = lineSeparator
389: + Utilities.spaces(margin);
390: }
391: try {
392: writer.setLineSeparator(bodyLineSeparator);
393: writer.write(String.valueOf(formatMessage(record)));
394: writer.setLineSeparator(lineSeparator);
395: writer.write('\n');
396: writer.flush();
397: } catch (IOException exception) {
398: // Should never happen, since we are writting into a StringBuffer.
399: throw new AssertionError(exception);
400: }
401: return buffer.toString();
402: }
403:
404: /**
405: * Setup a {@code MonolineFormatter} for the specified logger and its
406: * children. This method search for all instances of {@link ConsoleHandler}
407: * using the {@link SimpleFormatter}. If such instances are found, they are
408: * replaced by a single instance of {@code MonolineFormatter} writting
409: * to the {@linkplain System#out standard output stream} (instead of the
410: * {@linkplain System#err standard error stream}). If no such {@link ConsoleHandler}
411: * are found, then a new one is created with this {@code MonolineFormatter}.
412: * This action has no effect on any loggers outside the {@code base} namespace.
413: *
414: * @param base The base logger name to apply the change on (e.g. "org.geotools").
415: * @return The registered {@code MonolineFormatter} (never {@code null}).
416: * The formatter output can be configured using the {@link #setTimeFormat}
417: * and {@link #setSourceFormat} methods.
418: *
419: * @deprecated Use {@link Logging#forceMonolineConsoleOutput()} instead (which provides a
420: * central place for logging configuration), or {@link #init(String,Level)} if
421: * more control is wanted.
422: */
423: public static MonolineFormatter init(final String base) {
424: return init(base, null);
425: }
426:
427: /**
428: * Setup a {@code MonolineFormatter} for the specified logger and its children. This method
429: * search for all instances of {@link ConsoleHandler} using the {@link SimpleFormatter}. If
430: * such instances are found, they are replaced by a single instance of {@code MonolineFormatter}
431: * writting to the {@linkplain System#out standard output stream} (instead of the {@linkplain
432: * System#err standard error stream}). If no such {@link ConsoleHandler} are found, then a new
433: * one is created with this {@code MonolineFormatter}. This action has no effect on any loggers
434: * outside the {@code base} namespace.
435: * <p>
436: * In addition, this method can set the logger levels. If the level is non-null, then all
437: * {@link Handler}s using the monoline formatter will be set to the specified level. The
438: * logger named {@code base} will also be set to this level.
439: * <p>
440: * <b>Note:</b> Avoid non-null {@code level} argument as much as possible, since it overrides
441: * user's level setting for the {@code base} logger. A user trying to configure his logging
442: * properties may find confusing to see his setting ignored.
443: *
444: * @param base The base logger name to apply the change on (e.g. "org.geotools").
445: * @param level The desired level, or {@code null} if no level should be set.
446: * @return The registered {@code MonolineFormatter} (never {@code null}).
447: * The formatter output can be configured using the {@link #setTimeFormat}
448: * and {@link #setSourceFormat} methods.
449: */
450: public static MonolineFormatter init(final String base,
451: final Level level) {
452: MonolineFormatter monoline = null;
453: final Logger logger = Logger.getLogger(base);
454: Logger scan = logger;
455: do {
456: final Handler[] handlers = scan.getHandlers();
457: for (int i = 0; i < handlers.length; i++) {
458: /*
459: * Search for a ConsoleHandler. Search is performed in the target
460: * handler and all its parent loggers. When a ConsoleHandler is
461: * found, it will be replaced by the Stdout handler for 'logger'
462: * only.
463: */
464: Handler handler = handlers[i];
465: if (handler.getClass().equals(ConsoleHandler.class)) {
466: final Formatter formatter = handler.getFormatter();
467: if (formatter instanceof MonolineFormatter) {
468: /*
469: * A MonolineFormatter already existed. Set the level only for the first
470: * instance (only one instance should exists anyway), for consistency
471: * with the fact that this method returns only one MonolineFormatter for
472: * further configuration.
473: */
474: if (monoline == null) {
475: monoline = (MonolineFormatter) formatter;
476: if (level != null) {
477: handler.setLevel(level);
478: }
479: }
480: } else if (formatter.getClass().equals(
481: SimpleFormatter.class)) {
482: /*
483: * A ConsoleHandler using the SimpleFormatter has been found. Replace
484: * the SimpleFormatter by MonolineFormatter, creating it if necessary.
485: * If the handler creation fail with an exception, then we will continue
486: * to use the old J2SE handler instead.
487: */
488: if (monoline == null) {
489: monoline = new MonolineFormatter(base);
490: }
491: try {
492: handler = new Stdout(handler, monoline);
493: if (level != null) {
494: handler.setLevel(level);
495: }
496: } catch (UnsupportedEncodingException exception) {
497: unexpectedException(exception);
498: } catch (SecurityException exception) {
499: unexpectedException(exception);
500: }
501: }
502: }
503: logger.addHandler(handler);
504: }
505: } while ((scan = scan.getParent()) != null
506: && scan.getUseParentHandlers());
507: /*
508: * If no formatter has been found, create a new one and add the handler to the logger.
509: * If the creation fail with an exception, then we will continue to use the old J2SE
510: * handler instead.
511: */
512: if (monoline == null) {
513: monoline = new MonolineFormatter(base);
514: try {
515: final Handler handler = new Stdout(monoline);
516: if (level != null) {
517: handler.setLevel(level);
518: }
519: logger.addHandler(handler);
520: } catch (SecurityException exception) {
521: /*
522: * Returns without any change to the J2SE configuration. Note that the returned
523: * MonolineFormatter is really a dummy one, since we failed to register it. It
524: * will not prevent to program to work; just produces different logging outputs.
525: */
526: unexpectedException(exception);
527: return monoline;
528: }
529: }
530: logger.setUseParentHandlers(false);
531: if (level != null) {
532: logger.setLevel(level);
533: }
534: return monoline;
535: }
536:
537: /**
538: * Initialise the formatter for the "{@code org.geotools}" loggers.
539: *
540: * @deprecated Use {@link Logging#forceMonolineConsoleOutput()} instead (which provides a
541: * central place for logging configuration), or {@link #init(String,Level)} if
542: * more control is wanted.
543: */
544: public static void initGeotools() {
545: initGeotools(null);
546: }
547:
548: /**
549: * Initialise the formatter for the "{@code org.geotools}" loggers with the specified
550: * level. <strong>NOTE:</strong> Avoid this method as much as possible, since it overrides
551: * user's level setting for the "{@code org.geotools}" logger. A user trying to
552: * configure its logging properties may find confusing to see his setting ignored.
553: *
554: * @param level The logging level, or {@code null} if no level should be set.
555: *
556: * @deprecated Use {@link Logging#forceMonolineConsoleOutput(Level)} instead (which provides
557: * a central place for logging configuration), or {@link #init(String,Level)} if
558: * more control is wanted.
559: */
560: public static void initGeotools(final Level level) {
561: final MonolineFormatter f = init("org.geotools", level);
562: // As of new MonolineFormatter.init(...) specification, 'f' should never be null.
563: if (f.getSourceFormat() == null) {
564: // Set the source format only if the user didn't specified
565: // an explicit one in the jre/lib/logging.properties file.
566: f.setSourceFormat("class:long");
567: }
568: }
569:
570: /**
571: * Invoked when an error occurs during the initialization.
572: */
573: private static void unexpectedException(final Exception e) {
574: Logging.unexpectedException("org.geotools.util",
575: MonolineFormatter.class, "init", e);
576: }
577:
578: /**
579: * A {@link ConsoleHandler} sending output to {@link System#out} instead of
580: * {@link System#err} This handler will use a {@link MonolineFormatter}
581: * writting log message on a single line.
582: *
583: * @todo This class should subclass {@link ConsoleHandler}. Unfortunatly, this is currently
584: * not possible because {@link ConsoleHandler#setOutputStream} close {@link System#err}.
585: * If this bug get fixed, then {@link #close} no longer need to be overridden.
586: */
587: private static final class Stdout extends StreamHandler {
588: /**
589: * Constructs a handler.
590: *
591: * @param formatter The formatter to use.
592: */
593: public Stdout(final Formatter formatter) {
594: super (System.out, formatter);
595: }
596:
597: /**
598: * Constructs a handler.
599: *
600: * @param handler The handler to copy properties from.
601: * @param formatter The formatter to use.
602: */
603: public Stdout(final Handler handler, final Formatter formatter)
604: throws UnsupportedEncodingException {
605: super (System.out, formatter);
606: setErrorManager(handler.getErrorManager());
607: setFilter(handler.getFilter());
608: setLevel(handler.getLevel());
609: setEncoding(handler.getEncoding());
610: }
611:
612: /**
613: * Publish a {@link LogRecord} and flush the stream.
614: */
615: public void publish(final LogRecord record) {
616: super .publish(record);
617: flush();
618: }
619:
620: /**
621: * Override {@link StreamHandler#close} to do a flush but not to close the output stream.
622: * That is, we do <b>not</b> close {@link System#out}.
623: */
624: public void close() {
625: flush();
626: }
627: }
628: }
|