001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.util.logging;
017:
018: import java.lang.reflect.InvocationTargetException;
019: import java.util.Arrays;
020: import java.util.Comparator;
021: import java.util.logging.Level;
022: import java.util.logging.Logger;
023: import java.util.logging.LogRecord;
024: import java.lang.reflect.Method;
025:
026: import org.geotools.resources.XArray;
027: import org.geotools.resources.Utilities;
028: import org.geotools.resources.i18n.Errors;
029: import org.geotools.resources.i18n.ErrorKeys;
030:
031: /**
032: * A set of utilities method for configuring loggings in GeoTools. <strong>All GeoTools
033: * code should fetch their logger through a call to {@link #getLogger(String)}</strong>,
034: * not {@link Logger#getLogger(String)}. This is necessary in order to give GeoTools a
035: * chance to redirect log events to an other logging framework, for example
036: * <A HREF="http://jakarta.apache.org/commons/logging/">commons-logging</A>.
037: * <p>
038: * <b>Example:</b> In order to redirect every GeoTools log events to Commons-logging,
039: * invoke the following once at application startup:
040: *
041: * <blockquote><code>
042: * Logging.{@linkplain #GEOTOOLS}.{@linkplain #setLoggerFactory
043: * setLoggerFactory}("org.geotools.util.logging.CommonsLoggerFactory");
044: * </code></blockquote>
045: *
046: * @since 2.4
047: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/util/logging/Logging.java $
048: * @version $Id: Logging.java 27891 2007-11-14 14:10:48Z desruisseaux $
049: * @author Martin Desruisseaux
050: */
051: public final class Logging {
052: /**
053: * Compares {@link Logging} or {@link String} objects for alphabetical order.
054: */
055: private static final Comparator COMPARATOR = new Comparator() {
056: public int compare(final Object o1, final Object o2) {
057: final String n1 = (o1 instanceof Logging) ? ((Logging) o1).name
058: : o1.toString();
059: final String n2 = (o2 instanceof Logging) ? ((Logging) o2).name
060: : o2.toString();
061: return n1.compareTo(n2);
062: }
063: };
064:
065: /**
066: * An empty array of loggings. Also used for locks.
067: */
068: private static final Logging[] EMPTY = new Logging[0];
069:
070: /**
071: * Logging configuration that apply to all packages.
072: */
073: public static final Logging ALL = new Logging();
074: // NOTE: ALL must be created before any other static Logging constant.
075:
076: /**
077: * Logging configuration that apply only to GeoTools packages.
078: */
079: public static final Logging GEOTOOLS = getLogging("org.geotools");
080:
081: /**
082: * The name of the base package.
083: */
084: final String name;
085:
086: /**
087: * The children {@link Logging} objects.
088: * <p>
089: * The plain array used there is not efficient for adding new items (an {@code ArrayList}
090: * would be more efficient), but we assume that very few new items will be added. Furthermore
091: * a plain array is efficient for reading, and the later is way more common than the former.
092: */
093: private Logging[] children = EMPTY;
094:
095: /**
096: * The factory for creating loggers.
097: *
098: * @see #setLoggerFactory
099: */
100: private LoggerFactory factory;
101:
102: /**
103: * {@code true} if every {@link Logging} instances use the same {@link LoggerFactory}.
104: * This is an optimization for a very common case.
105: */
106: private static boolean sameLoggerFactory;
107:
108: /**
109: * Creates an instance for the root logger. This constructor should not be used
110: * for anything else than {@link #ALL} construction; use {@link #getLogging} instead.
111: */
112: private Logging() {
113: name = "";
114: }
115:
116: /**
117: * Creates an instance for the specified base logger. This constructor
118: * should not be public; use {@link #getLogging} instead.
119: *
120: * @param parent The parent {@code Logging} instance.
121: * @param name The logger name for the new instance.
122: */
123: private Logging(final Logging parent, final String name) {
124: this .name = name;
125: factory = parent.factory;
126: assert name.startsWith(parent.name) : name;
127: }
128:
129: /**
130: * Returns a logger for the specified name. If a {@linkplain LoggerFactory logger factory} has
131: * been set, then this method first {@linkplain LoggerFactory#getLogger ask to the factory}.
132: * It gives GeoTools a chance to redirect logging events to
133: * <A HREF="http://jakarta.apache.org/commons/logging/">commons-logging</A>
134: * or some equivalent framework.
135: * <p>
136: * If no factory was found or if the factory choose to not redirect the loggings, then this
137: * method returns the usual <code>{@linkplain Logger#getLogger Logger.getLogger}(name)</code>.
138: *
139: * @param name The logger name.
140: * @return A logger for the specified name.
141: */
142: public static Logger getLogger(final String name) {
143: synchronized (EMPTY) {
144: final Logging logging = sameLoggerFactory ? ALL
145: : getLogging(name, false);
146: if (logging != null) {
147: final LoggerFactory factory = logging.factory;
148: if (factory != null) {
149: final Logger logger = factory.getLogger(name);
150: if (logger != null) {
151: return logger;
152: }
153: }
154: }
155: }
156: return Logger.getLogger(name);
157: }
158:
159: /**
160: * Returns a {@code Logging} instance for the specified base logger. This instance is
161: * used for controlling logging configuration in GeoTools. For example methods like
162: * {@link #forceMonolineConsoleOutput} are invoked on a {@code Logging} instance.
163: * <p>
164: * {@code Logging} instances follow the same hierarchy than {@link Logger}, i.e.
165: * {@code "org.geotools"} is the parent of {@code "org.geotools.referencing"},
166: * {@code "org.geotools.metadata"}, <cite>etc</cite>.
167: *
168: * @param name The base logger name.
169: */
170: public static Logging getLogging(final String name) {
171: synchronized (EMPTY) {
172: return getLogging(name, true);
173: }
174: }
175:
176: /**
177: * Returns a logging instance for the specified base logger. If no instance if found for
178: * the specified name and {@code create} is {@code true}, then a new instance will be
179: * created. Otherwise the nearest parent is returned.
180: *
181: * @param root The root logger name.
182: * @param create {@code true} if this method is allowed to create new {@code Logging} instance.
183: */
184: private static Logging getLogging(final String base,
185: final boolean create) {
186: assert Thread.holdsLock(EMPTY);
187: Logging logging = ALL;
188: if (base.length() != 0) {
189: int offset = 0;
190: do {
191: Logging[] children = logging.children;
192: offset = base.indexOf('.', offset);
193: final String name = (offset >= 0) ? base.substring(0,
194: offset) : base;
195: int i = Arrays.binarySearch(children, name, COMPARATOR);
196: if (i < 0) {
197: // No exact match found.
198: if (!create) {
199: // We are not allowed to create new Logging instance.
200: // 'logging' is the nearest parent, so stop the loop now.
201: break;
202: }
203: i = ~i;
204: children = (Logging[]) XArray
205: .insert(children, i, 1);
206: children[i] = new Logging(logging, name);
207: logging.children = children;
208: }
209: logging = children[i];
210: } while (++offset != 0);
211: }
212: return logging;
213: }
214:
215: /**
216: * Returns the logger factory, or {@code null} if none. This method returns the logger set
217: * by the last call to {@link #setLoggerFactory} on this {@code Logging} instance or on one
218: * of its parent.
219: */
220: public LoggerFactory getLoggerFactory() {
221: synchronized (EMPTY) {
222: return factory;
223: }
224: }
225:
226: /**
227: * Sets a new logger factory for this {@code Logging} instance and every children. The
228: * specified factory will be used by <code>{@linkplain #getLogger getLogger}(name)</code>
229: * when {@code name} is this {@code Logging} name or one of its children.
230: */
231: public void setLoggerFactory(final LoggerFactory factory) {
232: synchronized (EMPTY) {
233: this .factory = factory;
234: for (int i = 0; i < children.length; i++) {
235: children[i].setLoggerFactory(factory);
236: }
237: sameLoggerFactory = sameLoggerFactory(ALL.children,
238: ALL.factory);
239: }
240: }
241:
242: /**
243: * Returns {@code true} if all children use the specified factory.
244: * Used in order to detect a possible optimization for this very common case.
245: */
246: private static boolean sameLoggerFactory(final Logging[] children,
247: final LoggerFactory factory) {
248: assert Thread.holdsLock(EMPTY);
249: for (int i = 0; i < children.length; i++) {
250: final Logging logging = children[i];
251: if (logging.factory != factory
252: || !sameLoggerFactory(logging.children, factory)) {
253: return false;
254: }
255: }
256: return true;
257: }
258:
259: /**
260: * Sets a new logger factory from a fully qualidifed class name. This method should be
261: * preferred to {@link #setLoggerFactory(LoggerFactory)} when the underlying logging
262: * framework is not garanteed to be on the classpath.
263: *
264: * @param className The fully qualified factory class name.
265: * @throws ClassNotFoundException if the specified class was not found.
266: * @throws IllegalArgumentException if the specified class is not a subclass of
267: * {@link LoggerFactory}, or if no public static {@code getInstance()} method
268: * has been found or can be executed.
269: */
270: public void setLoggerFactory(final String className)
271: throws ClassNotFoundException, IllegalArgumentException {
272: final LoggerFactory factory;
273: if (className == null) {
274: factory = null;
275: } else {
276: final Class factoryClass;
277: try {
278: factoryClass = Class.forName(className);
279: } catch (NoClassDefFoundError error) {
280: throw factoryNotFound(className, error);
281: }
282: if (!LoggerFactory.class.isAssignableFrom(factoryClass)) {
283: throw new IllegalArgumentException(Errors.format(
284: ErrorKeys.ILLEGAL_CLASS_$2, Utilities
285: .getShortName(factoryClass), Utilities
286: .getShortName(LoggerFactory.class)));
287: }
288: try {
289: final Method method = factoryClass.getMethod(
290: "getInstance", (Class[]) null);
291: factory = (LoggerFactory) method.invoke(null,
292: (Object[]) null);
293: } catch (Exception e) {
294: /*
295: * Catching java.lang.Exception is usually bad practice, but there is really a lot
296: * of checked exceptions when using reflection. Unfortunatly there is nothing like
297: * a "ReflectionException" parent class that we could catch instead. There is also
298: * a few unchecked exception that we want to process here, like ClassCastException.
299: */
300: Throwable cause = e;
301: if (e instanceof InvocationTargetException) {
302: cause = e.getCause(); // Simplify the stack trace.
303: }
304: if (cause instanceof ClassNotFoundException) {
305: throw (ClassNotFoundException) e;
306: }
307: if (cause instanceof NoClassDefFoundError) {
308: throw factoryNotFound(className,
309: (NoClassDefFoundError) cause);
310: }
311: throw new IllegalArgumentException(Errors.format(
312: ErrorKeys.CANT_CREATE_FACTORY_$1, className,
313: cause));
314: }
315: }
316: setLoggerFactory(factory);
317: }
318:
319: /**
320: * Wraps a unchecked {@link NoClassDefFoundError} into a checked {@link ClassNotFoundException}.
321: */
322: private static ClassNotFoundException factoryNotFound(String name,
323: NoClassDefFoundError error) {
324: return new ClassNotFoundException(Errors.format(
325: ErrorKeys.FACTORY_NOT_FOUND_$1, name), error);
326: }
327:
328: /**
329: * Configures the default {@linkplain java.util.logging.ConsoleHandler console handler} in
330: * order to log records on a single line instead of two lines. More specifically, for each
331: * {@link java.util.logging.ConsoleHandler} using a {@link java.util.logging.SimpleFormatter},
332: * this method replaces the simple formatter by an instance of {@link MonolineFormatter}. If
333: * no {@code ConsoleHandler} are found, then a new one is created.
334: * <p>
335: * <b>Note:</b> this method may have no effect if the loggings are redirected to an other
336: * logging framework, for example if {@link #redirectToCommonsLogging} has been invoked.
337: */
338: public void forceMonolineConsoleOutput() {
339: forceMonolineConsoleOutput(null);
340: }
341:
342: /**
343: * Same as {@link #forceMonolineConsoleOutput()}, but additionnaly set an optional logging
344: * level. If the specified level is non-null, then all {@link java.util.logging.Handler}s
345: * using the monoline formatter will be set to the specified level.
346: * <p>
347: * <b>Note:</b> Avoid this method as much as possible, since it overrides user's level
348: * setting. A user trying to configure his logging properties may find confusing to see
349: * his setting ignored.
350: *
351: * @see org.geotools.factory.GeoTools#init
352: */
353: public void forceMonolineConsoleOutput(final Level level) {
354: final Logger logger = Logger.getLogger(name); // Really Java logging, not the redirected one.
355: synchronized (EMPTY) {
356: final MonolineFormatter f = MonolineFormatter
357: .configureConsoleHandler(logger, level);
358: if (f.getSourceFormat() == null) {
359: // Set the source format only if the user didn't specified
360: // an explicit one in the jre/lib/logging.properties file.
361: f.setSourceFormat("class:short");
362: }
363: }
364: }
365:
366: /**
367: * Invoked when an unexpected error occurs. This method logs a message at the
368: * {@link Level#WARNING WARNING} level to the specified logger. The originating
369: * class name and method name are inferred from the error stack trace, using the
370: * first {@linkplain StackTraceElement stack trace element} for which the class
371: * name is inside a package or sub-package of the logger name. For example if
372: * the logger name is {@code "org.geotools.image"}, then this method will uses
373: * the first stack trace element where the fully qualified class name starts with
374: * {@code "org.geotools.image"} or {@code "org.geotools.image.io"}, but not
375: * {@code "org.geotools.imageio"}.
376: *
377: * @param logger Where to log the error.
378: * @param error The error that occured.
379: * @return {@code true} if the error has been logged, or {@code false} if the logger
380: * doesn't log anything at the {@link Level#WARNING WARNING} level.
381: */
382: public static boolean unexpectedException(final Logger logger,
383: final Throwable error) {
384: if (logger.isLoggable(Level.WARNING)) {
385: unexpectedException(logger.getName(), (String) null, null,
386: error);
387: return true;
388: }
389: return false;
390: }
391:
392: /**
393: * Invoked when an unexpected error occurs. This method logs a message at the
394: * {@link Level#WARNING WARNING} level to the specified logger. The originating
395: * class name and method name can optionnaly be specified. If any of them is
396: * {@code null}, then it will be inferred from the error stack trace as in
397: * {@link #unexpectedException(Logger, Throwable)}.
398: * <p>
399: * Explicit value for class and method names are sometime preferred to automatic
400: * inference for the following reasons:
401: *
402: * <ul>
403: * <li><p>Automatic inference is not 100% reliable, since the Java Virtual Machine
404: * is free to omit stack frame in optimized code.</p></li>
405: * <li><p>When an exception occured in a private method used internally by a public
406: * method, we sometime want to log the warning for the public method instead,
407: * since the user is not expected to know anything about the existence of the
408: * private method. If a developper really want to know about the private method,
409: * the stack trace is still available anyway.</p></li>
410: * </ul>
411: *
412: * @param logger Where to log the error.
413: * @param classe The class where the error occurred, or {@code null}.
414: * @param method The method where the error occurred, or {@code null}.
415: * @param error The error.
416: */
417: public static void unexpectedException(final Logger logger,
418: final Class classe, final String method,
419: final Throwable error) {
420: // TODO: Refactor in order to use directly the logger after we removed the deprecated method.
421: unexpectedException(logger.getName(), classe, method, error);
422: }
423:
424: /**
425: * Invoked when an unexpected error occurs. This method logs a message at the
426: * {@link Level#WARNING WARNING} level to the logger for the specified package
427: * name. The originating class name and method name can optionnaly be specified.
428: * If any of them is {@code null}, then it will be inferred from the error stack
429: * trace as in {@link #unexpectedException(Logger, Throwable)}.
430: *
431: * @param paquet The package where the error occurred, or {@code null}. This
432: * information is used for fetching an appropriate {@link Logger}
433: * for logging the error.
434: * @param classe The class where the error occurred, or {@code null}.
435: * @param method The method where the error occurred, or {@code null}.
436: * @param error The error.
437: */
438: public static void unexpectedException(final String paquet,
439: final Class classe, final String method,
440: final Throwable error) {
441: // TODO: use getSimpleName() or getCanonicalName() when we will be allowed to target J2SE 1.5.
442: unexpectedException(paquet, (classe != null) ? classe.getName()
443: : (String) null, method, error);
444: }
445:
446: /**
447: * Same as {@link #unexpectedException(String, Class, String, Throwable)
448: * unexpectedException(..., Class, ...)} except that the class name is
449: * specified as a string.
450: *
451: * @param paquet The package where the error occurred, or {@code null}. This
452: * information is used for fetching an appropriate {@link Logger}
453: * for logging the error.
454: * @param classe The class where the error occurred, or {@code null}.
455: * @param method The method where the error occurred, or {@code null}.
456: * @param error The error.
457: */
458: private static void unexpectedException(String paquet,
459: String classe, String method, final Throwable error) {
460: final LogRecord record = Utilities.getLogRecord(error);
461: if (paquet == null || classe == null || method == null) {
462: final StackTraceElement[] elements = error.getStackTrace();
463: for (int i = 0; i < elements.length; i++) {
464: final StackTraceElement e = elements[i];
465: final String c = e.getClassName();
466: if (paquet != null) {
467: if (!c.startsWith(paquet)) {
468: continue;
469: }
470: final int lg = paquet.length();
471: if (c.length() > lg
472: && Character.isJavaIdentifierPart(c
473: .charAt(lg))) {
474: continue;
475: }
476: }
477: if (classe != null) {
478: if (!c.endsWith(classe)) {
479: continue;
480: }
481: final int lg = c.length() - classe.length() - 1;
482: if (c.length() >= 0
483: && Character.isJavaIdentifierPart(c
484: .charAt(lg))) {
485: continue;
486: }
487: }
488: final String m = e.getMethodName();
489: if (method != null) {
490: if (!m.equals(method)) {
491: continue;
492: }
493: }
494: final int separator = c.lastIndexOf('.');
495: if (paquet == null) {
496: paquet = (separator >= 1) ? c.substring(0,
497: separator - 1) : "";
498: }
499: if (classe == null) {
500: classe = c.substring(separator + 1);
501: }
502: if (method == null) {
503: method = m;
504: }
505: break;
506: }
507: }
508: record.setSourceClassName(classe);
509: record.setSourceMethodName(method);
510: record.setThrown(error);
511: Logger.getLogger(paquet).log(record);
512: }
513: }
|