/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.util.logging; import java.util.Arrays; import java.util.Comparator; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.LogRecord; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import org.geotools.resources.XArray; import org.geotools.resources.Classes; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * A set of utilities method for configuring loggings in GeoTools. <strong>All GeoTools * code should fetch their logger through a call to {@link #getLogger(String)}</strong>, * not {@link Logger#getLogger(String)}. This is necessary in order to give GeoTools a * chance to redirect log events to an other logging framework, for example * <A HREF="http://jakarta.apache.org/commons/logging/">commons-logging</A>. * <p> * <b>Example:</b> In order to redirect every GeoTools log events to Commons-logging, * invoke the following once at application startup: * * <blockquote><code> * Logging.{@linkplain #GEOTOOLS}.{@linkplain #setLoggerFactory * setLoggerFactory}("org.geotools.util.logging.CommonsLoggerFactory"); * </code></blockquote> * * @since 2.4 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public final class Logging { /** * Compares {@link Logging} or {@link String} objects for alphabetical order. */ private static final Comparator<Object> COMPARATOR = new Comparator<Object>() { public int compare(final Object o1, final Object o2) { final String n1 = (o1 instanceof Logging) ? ((Logging) o1).name : o1.toString(); final String n2 = (o2 instanceof Logging) ? ((Logging) o2).name : o2.toString(); return n1.compareTo(n2); } }; /** * An empty array of loggings. Also used for locks. */ private static final Logging[] EMPTY = new Logging[0]; /** * Logging configuration that apply to all packages. */ public static final Logging ALL = new Logging(); // NOTE: ALL must be created before any other static Logging constant. /** * Logging configuration that apply only to GeoTools packages. */ public static final Logging GEOTOOLS = getLogging("org.geotools"); /** * The name of the base package. */ final String name; /** * The children {@link Logging} objects. * <p> * The plain array used there is not efficient for adding new items (an {@code ArrayList} * would be more efficient), but we assume that very few new items will be added. Furthermore * a plain array is efficient for reading, and the later is way more common than the former. */ private Logging[] children = EMPTY; /** * The factory for creating loggers. * * @see #setLoggerFactory */ private LoggerFactory factory; /** * {@code true} if every {@link Logging} instances use the same {@link LoggerFactory}. * This is an optimization for a very common case. */ private static boolean sameLoggerFactory = true; /** * Creates an instance for the root logger. This constructor should not be used * for anything else than {@link #ALL} construction; use {@link #getLogging} instead. */ private Logging() { name = ""; } /** * Creates an instance for the specified base logger. This constructor * should not be public; use {@link #getLogging} instead. * * @param parent The parent {@code Logging} instance. * @param name The logger name for the new instance. */ private Logging(final Logging parent, final String name) { this.name = name; factory = parent.factory; assert name.startsWith(parent.name) : name; } /** * Returns a logger for the specified class. This convenience method invokes * {@link #getLogger(String)} with the package name as the logger name. * * @param classe The class for which to obtain a logger. * @return A logger for the specified class. * * @since 2.5 */ public static Logger getLogger(final Class<?> classe) { String name = classe.getName(); final int separator = name.lastIndexOf('.'); name = (separator >= 1) ? name.substring(0, separator) : ""; return getLogger(name); } /** * Returns a logger for the specified name. If a {@linkplain LoggerFactory logger factory} has * been set, then this method first {@linkplain LoggerFactory#getLogger ask to the factory}. * It gives GeoTools a chance to redirect logging events to * <A HREF="http://jakarta.apache.org/commons/logging/">commons-logging</A> * or some equivalent framework. * <p> * If no factory was found or if the factory choose to not redirect the loggings, then this * method returns the usual <code>{@linkplain Logger#getLogger Logger.getLogger}(name)</code>. * * @param name The logger name. * @return A logger for the specified name. */ public static Logger getLogger(final String name) { synchronized (EMPTY) { final Logging logging = sameLoggerFactory ? ALL : getLogging(name, false); if (logging != null) { // Paranoiac check ('getLogging' should not returns null). final LoggerFactory factory = logging.factory; assert getLogging(name, false).factory == factory : name; if (factory != null) { final Logger logger = factory.getLogger(name); if (logger != null) { return logger; } } } } return Logger.getLogger(name); } /** * Returns a {@code Logging} instance for the specified base logger. This instance is * used for controlling logging configuration in GeoTools. For example methods like * {@link #forceMonolineConsoleOutput} are invoked on a {@code Logging} instance. * <p> * {@code Logging} instances follow the same hierarchy than {@link Logger}, i.e. * {@code "org.geotools"} is the parent of {@code "org.geotools.referencing"}, * {@code "org.geotools.metadata"}, <cite>etc</cite>. * * @param name The base logger name. */ public static Logging getLogging(final String name) { synchronized (EMPTY) { return getLogging(name, true); } } /** * Returns a logging instance for the specified base logger. If no instance if found for * the specified name and {@code create} is {@code true}, then a new instance will be * created. Otherwise the nearest parent is returned. * * @param root The root logger name. * @param create {@code true} if this method is allowed to create new {@code Logging} instance. */ private static Logging getLogging(final String base, final boolean create) { assert Thread.holdsLock(EMPTY); Logging logging = ALL; if (base.length() != 0) { int offset = 0; do { Logging[] children = logging.children; offset = base.indexOf('.', offset); final String name = (offset >= 0) ? base.substring(0, offset) : base; int i = Arrays.binarySearch(children, name, COMPARATOR); if (i < 0) { // No exact match found. if (!create) { // We are not allowed to create new Logging instance. // 'logging' is the nearest parent, so stop the loop now. break; } i = ~i; children = XArray.insert(children, i, 1); children[i] = new Logging(logging, name); logging.children = children; } logging = children[i]; } while (++offset != 0); } return logging; } /** * For testing purpose only; don't make this method public. */ final Logging[] getChildren() { synchronized (EMPTY) { return children.clone(); } } /** * Returns the logger factory, or {@code null} if none. This method returns the logger set * by the last call to {@link #setLoggerFactory} on this {@code Logging} instance or on one * of its parent. */ public LoggerFactory getLoggerFactory() { synchronized (EMPTY) { return factory; } } /** * Sets a new logger factory for this {@code Logging} instance and every children. The * specified factory will be used by <code>{@linkplain #getLogger(String) getLogger}(name)</code> * when {@code name} is this {@code Logging} name or one of its children. */ public void setLoggerFactory(final LoggerFactory factory) { synchronized (EMPTY) { this.factory = factory; for (int i=0; i<children.length; i++) { children[i].setLoggerFactory(factory); } sameLoggerFactory = sameLoggerFactory(ALL.children, ALL.factory); } } /** * Returns {@code true} if all children use the specified factory. * Used in order to detect a possible optimization for this very common case. */ private static boolean sameLoggerFactory(final Logging[] children, final LoggerFactory factory) { assert Thread.holdsLock(EMPTY); for (int i=0; i<children.length; i++) { final Logging logging = children[i]; if (logging.factory != factory || !sameLoggerFactory(logging.children, factory)) { return false; } } return true; } /** * Sets a new logger factory from a fully qualidifed class name. This method should be * preferred to {@link #setLoggerFactory(LoggerFactory)} when the underlying logging * framework is not garanteed to be on the classpath. * * @param className The fully qualified factory class name. * @throws ClassNotFoundException if the specified class was not found. * @throws IllegalArgumentException if the specified class is not a subclass of * {@link LoggerFactory}, or if no public static {@code getInstance()} method * has been found or can be executed. */ public void setLoggerFactory(final String className) throws ClassNotFoundException, IllegalArgumentException { final LoggerFactory factory; if (className == null) { factory = null; } else { final Class<?> factoryClass; try { factoryClass = Class.forName(className); } catch (NoClassDefFoundError error) { throw factoryNotFound(className, error); } if (!LoggerFactory.class.isAssignableFrom(factoryClass)) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_CLASS_$2, factoryClass, LoggerFactory.class)); } try { final Method method = factoryClass.getMethod("getInstance", (Class[]) null); factory = LoggerFactory.class.cast(method.invoke(null, (Object[]) null)); } catch (Exception e) { /* * Catching java.lang.Exception is usually bad practice, but there is really a lot * of checked exceptions when using reflection. Unfortunatly there is nothing like * a "ReflectionException" parent class that we could catch instead. There is also * a few unchecked exception that we want to process here, like ClassCastException. */ Throwable cause = e; if (e instanceof InvocationTargetException) { cause = e.getCause(); // Simplify the stack trace. } if (cause instanceof ClassNotFoundException) { throw (ClassNotFoundException) e; } if (cause instanceof NoClassDefFoundError) { throw factoryNotFound(className, (NoClassDefFoundError) cause); } throw new IllegalArgumentException(Errors.format( ErrorKeys.CANT_CREATE_FACTORY_$1, className, cause)); } } setLoggerFactory(factory); } /** * Wraps a unchecked {@link NoClassDefFoundError} into a checked {@link ClassNotFoundException}. */ private static ClassNotFoundException factoryNotFound(String name, NoClassDefFoundError error) { return new ClassNotFoundException(Errors.format(ErrorKeys.FACTORY_NOT_FOUND_$1, name), error); } /** * Configures the default {@linkplain java.util.logging.ConsoleHandler console handler} in * order to log records on a single line instead of two lines. More specifically, for each * {@link java.util.logging.ConsoleHandler} using a {@link java.util.logging.SimpleFormatter}, * this method replaces the simple formatter by an instance of {@link MonolineFormatter}. If * no {@code ConsoleHandler} are found, then a new one is created. * <p> * <b>Note:</b> this method may have no effect if the loggings are redirected to an other * logging framework, for example if {@link #redirectToCommonsLogging} has been invoked. */ public void forceMonolineConsoleOutput() { forceMonolineConsoleOutput(null); } /** * Same as {@link #forceMonolineConsoleOutput()}, but additionnaly set an optional logging * level. If the specified level is non-null, then all {@link java.util.logging.Handler}s * using the monoline formatter will be set to the specified level. * <p> * <b>Note:</b> Avoid this method as much as possible, since it overrides user's level * setting. A user trying to configure his logging properties may find confusing to see * his setting ignored. * * @see org.geotools.factory.GeoTools#init */ public void forceMonolineConsoleOutput(final Level level) { final Logger logger = Logger.getLogger(name); // Really Java logging, not the redirected one. synchronized (EMPTY) { final MonolineFormatter f = MonolineFormatter.configureConsoleHandler(logger, level); if (f.getSourceFormat() == null) { // Set the source format only if the user didn't specified // an explicit one in the jre/lib/logging.properties file. f.setSourceFormat("class:short"); } if (level != null) { // If a level was specified, changes to a finer level if needed // (e.g. from FINE to FINER, but not the opposite). final Level current = logger.getLevel(); if (current == null || current.intValue() > level.intValue()) { logger.setLevel(level); } } } } /** * Invoked when an unexpected error occurs. This method logs a message at the * {@link Level#WARNING WARNING} level to the specified logger. The originating * class name and method name are inferred from the error stack trace, using the * first {@linkplain StackTraceElement stack trace element} for which the class * name is inside a package or sub-package of the logger name. For example if * the logger name is {@code "org.geotools.image"}, then this method will uses * the first stack trace element where the fully qualified class name starts with * {@code "org.geotools.image"} or {@code "org.geotools.image.io"}, but not * {@code "org.geotools.imageio"}. * * @param logger Where to log the error. * @param error The error that occured. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the {@link Level#WARNING WARNING} level. */ public static boolean unexpectedException(final Logger logger, final Throwable error) { return unexpectedException(logger, null, null, error, Level.WARNING); } /** * Invoked when an unexpected error occurs. This method logs a message at the * {@link Level#WARNING WARNING} level to the specified logger. The originating * class name and method name can optionnaly be specified. If any of them is * {@code null}, then it will be inferred from the error stack trace as in * {@link #unexpectedException(Logger, Throwable)}. * <p> * Explicit value for class and method names are sometime preferred to automatic * inference for the following reasons: * * <ul> * <li><p>Automatic inference is not 100% reliable, since the Java Virtual Machine * is free to omit stack frame in optimized code.</p></li> * <li><p>When an exception occured in a private method used internally by a public * method, we sometime want to log the warning for the public method instead, * since the user is not expected to know anything about the existence of the * private method. If a developper really want to know about the private method, * the stack trace is still available anyway.</p></li> * </ul> * * @param logger Where to log the error. * @param classe The class where the error occurred, or {@code null}. * @param method The method where the error occurred, or {@code null}. * @param error The error. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the {@link Level#WARNING WARNING} level. */ public static boolean unexpectedException(final Logger logger, final Class<?> classe, final String method, final Throwable error) { final String classname = (classe != null) ? classe.getName() : null; return unexpectedException(logger, classname, method, error, Level.WARNING); } /** * Invoked when an unexpected error occurs. This method logs a message at the * {@link Level#WARNING WARNING} level to the logger for the specified package * name. The originating class name and method name can optionnaly be specified. * If any of them is {@code null}, then it will be inferred from the error stack * trace as in {@link #unexpectedException(Logger, Throwable)}. * * @param paquet The package where the error occurred, or {@code null}. This * information is used for fetching an appropriate {@link Logger} * for logging the error. * @param classe The class where the error occurred, or {@code null}. * @param method The method where the error occurred, or {@code null}. * @param error The error. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the {@link Level#WARNING WARNING} level. * * @deprecated Use one of the other {@code unexpectedException} methods instead. */ public static boolean unexpectedException(final String paquet, final Class<?> classe, final String method, final Throwable error) { final Logger logger = (paquet != null) ? getLogger(paquet) : null; return unexpectedException(logger, classe, method, error); } /** * Invoked when an unexpected error occurs. This method logs a message at the * {@link Level#WARNING WARNING} level to a logger inferred from the given class. * * @param classe The class where the error occurred. * @param method The method where the error occurred, or {@code null}. * @param error The error. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the {@link Level#WARNING WARNING} level. * * @since 2.5 */ public static boolean unexpectedException(Class<?> classe, String method, Throwable error) { return unexpectedException((Logger) null, classe, method, error); } /** * Implementation of {@link #unexpectedException(Logger, Class, String, Throwable)}. * * @param logger Where to log the error, or {@code null}. * @param classe The fully qualified class name where the error occurred, or {@code null}. * @param method The method where the error occurred, or {@code null}. * @param error The error. * @param level The logging level. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the specified level. */ private static boolean unexpectedException(Logger logger, String classe, String method, final Throwable error, final Level level) { /* * Checks if loggable, inferring the logger from the classe name if needed. */ if (error == null) { return false; } if (logger == null && classe != null) { final int separator = classe.lastIndexOf('.'); final String paquet = (separator >= 1) ? classe.substring(0, separator-1) : ""; logger = getLogger(paquet); } if (logger != null && !logger.isLoggable(level)) { return false; } /* * Loggeable, so complete the null argument from the stack trace if we can. */ if (logger==null || classe==null || method==null) { String paquet = (logger != null) ? logger.getName() : null; final StackTraceElement[] elements = error.getStackTrace(); for (int i=0; i<elements.length; i++) { /* * Searchs for the first stack trace element with a classname matching the * expected one. We compare preferably against the name of the class given * in argument, or against the logger name (taken as the package name) otherwise. */ final StackTraceElement element = elements[i]; final String classname = element.getClassName(); if (classe != null) { if (!classname.equals(classe)) { continue; } } else if (paquet != null) { if (!classname.startsWith(paquet)) { continue; } final int length = paquet.length(); if (classname.length() > length) { // We expect '.' but we accept also '$' or end of string. final char separator = classname.charAt(length); if (Character.isJavaIdentifierPart(separator)) { continue; } } } /* * Now that we have a stack trace element from the expected class (or any * element if we don't know the class), make sure that we have the right method. */ final String methodName = element.getMethodName(); if (method != null && !methodName.equals(method)) { continue; } /* * Now computes every values that are null, and stop the loop. */ if (paquet == null) { final int separator = classname.lastIndexOf('.'); paquet = (separator >= 1) ? classname.substring(0, separator-1) : ""; logger = getLogger(paquet); if (!logger.isLoggable(level)) { return false; } } if (classe == null) { classe = classname; } if (method == null) { method = methodName; } break; } /* * The logger may stay null if we have been unable to find a suitable * stack trace. Fallback on the global logger. * * TODO: Use GLOBAL_LOGGER_NAME constant when we will be allowed to target Java 6. */ if (logger == null) { logger = getLogger("global"); if (!logger.isLoggable(level)) { return false; } } } /* * Now prepare the log message. If we have been unable to figure out a source class and * method name, we will fallback on Java logging default mechanism, which may returns a * less relevant name than our attempt to use the logger name as the package name. */ final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(error)); final String message = error.getLocalizedMessage(); if (message != null) { buffer.append(": ").append(message); } final LogRecord record = new LogRecord(level, buffer.toString()); if (classe != null) { record.setSourceClassName(classe); } if (method != null) { record.setSourceMethodName(method); } if (level.intValue() > 500) { record.setThrown(error); } record.setLoggerName(logger.getName()); logger.log(record); return true; } /** * Invoked when a recoverable error occurs. This method is similar to * {@link #unexpectedException(Logger,Class,String,Throwable) unexpectedException} * except that it doesn't log the stack trace and uses a lower logging level. * * @param logger Where to log the error. * @param classe The class where the error occurred. * @param method The method name where the error occurred. * @param error The error. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the specified level. * * @since 2.5 */ public static boolean recoverableException(final Logger logger, final Class<?> classe, final String method, final Throwable error) { final String classname = (classe != null) ? classe.getName() : null; return unexpectedException(logger, classname, method, error, Level.FINE); } /** * Invoked when a recoverable error occurs. This method is similar to * {@link #unexpectedException(Class,String,Throwable) unexpectedException} * except that it doesn't log the stack trace and uses a lower logging level. * * @param classe The class where the error occurred. * @param method The method name where the error occurred. * @param error The error. * @return {@code true} if the error has been logged, or {@code false} if the logger * doesn't log anything at the specified level. * * @since 2.5 */ public static boolean recoverableException(final Class<?> classe, final String method, final Throwable error) { return recoverableException(null, classe, method, error); } }