From sis-commits-return-520-apmail-incubator-sis-commits-archive=incubator.apache.org@incubator.apache.org Thu Sep 20 10:36:55 2012 Return-Path: X-Original-To: apmail-incubator-sis-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-sis-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B7EDBDDE9 for ; Thu, 20 Sep 2012 10:36:55 +0000 (UTC) Received: (qmail 12125 invoked by uid 500); 20 Sep 2012 10:36:55 -0000 Delivered-To: apmail-incubator-sis-commits-archive@incubator.apache.org Received: (qmail 12094 invoked by uid 500); 20 Sep 2012 10:36:55 -0000 Mailing-List: contact sis-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: sis-dev@incubator.apache.org Delivered-To: mailing list sis-commits@incubator.apache.org Received: (qmail 12085 invoked by uid 99); 20 Sep 2012 10:36:55 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 20 Sep 2012 10:36:55 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 20 Sep 2012 10:36:44 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 1B60D238890B; Thu, 20 Sep 2012 10:35:59 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1387950 [1/2] - in /incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util: ./ logging/ Date: Thu, 20 Sep 2012 10:35:58 -0000 To: sis-commits@incubator.apache.org From: desruisseaux@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120920103559.1B60D238890B@eris.apache.org> Author: desruisseaux Date: Thu Sep 20 10:35:57 2012 New Revision: 1387950 URL: http://svn.apache.org/viewvc?rev=1387950&view=rev Log: Added org.apache.sis.util.logging package. Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java (with props) incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java (with props) incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java (with props) incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java (with props) incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java (with props) incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java (with props) incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/package-info.java (with props) Modified: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java Modified: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java?rev=1387950&r1=1387949&r2=1387950&view=diff ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java (original) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java Thu Sep 20 10:35:57 2012 @@ -39,6 +39,9 @@ package org.apache.sis.util; * Perform argument checks and throw {@link IllegalArgumentException} if needed. * {@link org.apache.sis.util.Exceptions} * Format a stack trace summary or change the exception message. + * {@link org.apache.sis.util.logging.Logging} + * Get a JDK {@linkplain java.util.logging.Logger logger}, which may be a wrapper around + * the Apache Commons Logging or Log4J framework. * * * @author Martin Desruisseaux (Geomatys) Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java?rev=1387950&view=auto ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java (added) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java Thu Sep 20 10:35:57 2012 @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.util.logging; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Redirects logging to two loggers. This is used only when more than one {@link LoggerFactory} + * is found on the classpath. This should never happen, but if it happen anyway we will send the + * log records to all registered loggers in order to have a behavior slightly more determinist + * than picking an arbitrary logger. + * + * @author Martin Desruisseaux (IRD, Geomatys) + * @since 0.3 (derived from geotk-3.20) + * @version 0.3 + * @module + */ +final class DualLogger extends LoggerAdapter { + /** + * The two loggers. + */ + private final Logger first, second; + + /** + * Creates a new logger which will redirects the event to the two specified loggers. + */ + DualLogger(final String name, final Logger first, final Logger second) { + super(name); + this.first = first; + this.second = second; + } + + /** + * Sets the level for the two loggers. + */ + @Override + public void setLevel(final Level level) { + first .setLevel(level); + second.setLevel(level); + } + + /** + * Returns the finest level from the two loggers. + */ + @Override + public Level getLevel() { + final Level v1 = first .getLevel(); + final Level v2 = second.getLevel(); + return (v1.intValue() < v2.intValue()) ? v1 : v2; + } + + /** + * Returns {@code true} if the specified level is loggable by at least one logger. + */ + @Override + public boolean isLoggable(final Level level) { + return first.isLoggable(level) || second.isLoggable(level); + } + + /** + * Logs a record at the specified level. + */ + @Override + public void log(final Level level, final String message) { + first .log(level, message); + second.log(level, message); + } + + /** + * Logs a record at the specified level. + */ + @Override + public void log(final Level level, final String message, final Throwable thrown) { + first .log(level, message, thrown); + second.log(level, message, thrown); + } + + @Override public void severe (String message) {first.severe (message); second.severe (message);} + @Override public void warning(String message) {first.warning(message); second.warning(message);} + @Override public void info (String message) {first.info (message); second.info (message);} + @Override public void config (String message) {first.config (message); second.config (message);} + @Override public void fine (String message) {first.fine (message); second.fine (message);} + @Override public void finer (String message) {first.finer (message); second.finer (message);} + @Override public void finest (String message) {first.finest (message); second.finest (message);} +} Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java?rev=1387950&view=auto ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java (added) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java Thu Sep 20 10:35:57 2012 @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.util.logging; + +import java.util.logging.Logger; + + +/** + * Redirects logging to two loggers. This is used only when more than one {@link LoggerFactory} + * is found on the classpath. This should never happen, but if it happen anyway we will send the + * log records to all registered loggers in order to have a behavior slightly more determinist + * than picking an arbitrary logger. + * + * @author Martin Desruisseaux (IRD, Geomatys) + * @since 0.3 (derived from geotk-3.20) + * @version 0.3 + * @module + */ +final class DualLoggerFactory extends LoggerFactory { + /** + * The factories of loggers on which to delegate logging events. + */ + private final LoggerFactory first, second; + + /** + * Creates a new factory which will delegate the logging events to the loggers + * created by the two given factories. + */ + DualLoggerFactory(final LoggerFactory first, final LoggerFactory second) { + super(DualLogger.class); + this.first = first; + this.second = second; + } + + /** + * Returns the implementation to use for the logger of the specified name, + * or {@code null} if the logger would delegates to Java logging anyway. + */ + @Override + protected DualLogger getImplementation(final String name) { + return new DualLogger(name, first.getLogger(name), second.getLogger(name)); + } + + /** + * Wraps the specified {@linkplain #getImplementation implementation} in a Java logger. + */ + @Override + protected Logger wrap(final String name, final DualLogger implementation) { + return implementation; + } + + /** + * Returns the {@linkplain #getImplementation implementation} wrapped by the specified logger, + * or {@code null} if none. + */ + @Override + protected DualLogger unwrap(final Logger logger) { + if (logger instanceof DualLogger) { + return (DualLogger) logger; + } + return null; + } +} Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java?rev=1387950&view=auto ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java (added) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java Thu Sep 20 10:35:57 2012 @@ -0,0 +1,726 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.util.logging; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.regex.Pattern; + +import org.apache.sis.util.Debug; + + +/** + * An adapter that redirect all JDK logging events to an other logging framework. This + * class redefines the {@link #severe(String) severe}, {@link #warning(String) warning}, + * {@link #info(String) info}, {@link #config(String) config}, {@link #fine(String) fine}, + * {@link #finer(String) finer} and {@link #finest(String) finest} methods as abstract + * ones. Subclasses should implement those methods in order to map JDK logging levels to + * the backend logging framework. + *

+ * All {@link #log(Level,String) log} methods are overridden in order to redirect to one of the + * above-cited methods. Note that this is the opposite approach than the JDK logging framework + * one, which implements everything on top of {@link Logger#log(LogRecord)}. This adapter is + * defined in terms of {@link #severe(String) severe} … {@link #finest(String) finest} methods + * instead because external frameworks like Commons-logging + * don't work with {@link LogRecord}, and sometime provides nothing else than convenience methods + * equivalent to {@link #severe(String) severe} … {@link #finest(String) finest}. + * + * {@section Restrictions} + * Because the configuration is expected to be fully controlled by the external logging + * framework, every configuration methods inherited from {@link Logger} are disabled: + *

+ *

    + *
  • {@link #addHandler(Handler)} + * since the handling is performed by the external framework.
  • + * + *
  • {@link #setUseParentHandlers(boolean)} + * since this adapter never delegates to the parent handlers. This is consistent with the + * previous item and avoid mixing loggings from the external framework with JDK loggings.
  • + * + *
  • {@link #setParent(Logger)} + * since this adapter should not inherits any configuration from a parent logger using the + * JDK logging framework.
  • + * + *
  • {@link #setFilter(Filter)} + * for keeping this {@code LoggerAdapter} simple.
  • + *
+ *

+ * Since {@code LoggerAdapter}s do not hold any configuration by themselves, it is not strictly + * necessary to {@linkplain java.util.logging.LogManager#addLogger add them to the log manager}. + * The adapters can be created, garbage-collected and recreated again while preserving their + * behavior since their configuration is entirely contained in the external logging framework. + * + * {@section Localization} + * This logger is always created without resource bundles. Localizations must be performed through + * explicit calls to {@code logrb} or {@link #log(LogRecord)} methods. This is sufficient for + * SIS needs, which performs all localizations through the later. Note that those methods + * will be slower in this {@code LoggerAdapter} than the default {@link Logger} because this + * adapter localizes and formats records immediately instead of letting the {@linkplain Handler} + * performs this work only if needed. + * + * {@section Logging levels} + * If a log record {@linkplain Level level} is not one of the predefined ones, then this class + * maps to the first level below the specified one. For example if a log record has some level + * between {@link Level#FINE FINE} and {@link Level#FINER FINER}, then the {@link #finer finer} + * method will be invoked. See {@link #isLoggable(Level)} for implementation tips taking advantage + * of this rule. + * + * @author Martin Desruisseaux (IRD, Geomatys) + * @since 0.3 (derived from geotk-2.4) + * @version 0.3 + * @module + * + * @see Logging + */ +public abstract class LoggerAdapter extends Logger { + /** + * The pattern to use for detecting {@link MessageFormat}. + */ + private static final Pattern MESSAGE_FORMAT = Pattern.compile("\\{\\d+\\}"); + + /** + * Creates a new logger. + * + * @param name The logger name. + */ + protected LoggerAdapter(final String name) { + super(name, null); + /* + * Must invokes the super-class method, because LoggerAdapter overrides it as a no-op. + */ + super.setUseParentHandlers(false); + /* + * Sets the level to ALL as a matter of principle, but we will never check the level + * anyway (we will let the external logging framework do its own check). Note that we + * must invoke the method in the super-class because we want to set the java logging + * level, not the external framework level. + */ + super.setLevel(Level.ALL); + } + + /** + * Sets the level for this logger. Subclasses must redirect the call to the external + * logging framework, or do nothing if the level can not be changed programmatically. + * + * @param level The new value for the log level (may be null). + */ + @Override + public abstract void setLevel(Level level); + + /** + * Returns the level for this logger. Subclasses shall get this level from the + * external logging framework. + * + * @return The logger's level. + */ + @Override + public abstract Level getLevel(); + + /** + * Returns the level for {@link #entering}, {@link #exiting} and {@link #throwing} methods. + * The default implementation returns {@link Level#FINER}, which is consistent with the + * value used in the JDK logging framework. Subclasses should override this method if + * a different debug level is wanted. + * + * @return The level to use for debugging informations. + */ + @Debug + protected Level getDebugLevel() { + return Level.FINER; + } + + /** + * Returns {@code true} if the specified level is loggable. + * + * {@section Implementation tip} + * Given that {@link Level#intValue} for all predefined levels are documented in the {@link Level} + * specification and are multiple of 100, given that integer divisions are rounded toward zero and + * given rule documented in this class javadoc, then logging levels + * can be efficiently mapped to predefined levels using {@code switch} statements as below. This + * statement has good chances to be compiled to the {@code tableswitch} bytecode rather than + * {@code lookupswitch} (see + * Compiling + * Switches in The Java Virtual Machine Specification). + * + * {@preformat java + * public boolean isLoggable(Level level) { + * final int n = level.intValue(); + * switch (n / 100) { + * default: { + * // MAX_VALUE is a special value for Level.OFF. Otherwise and + * // if positive, fallthrough since we are greater than SEVERE. + * switch (n) { + * case Integer.MIN_VALUE: return true; // Level.ALL + * case Integer.MAX_VALUE: return false; // Level.OFF + * default: if (n < 0) return false; + * } + * } + * case 10: return isSevereEnabled(); + * case 9: return isWarningEnabled(); + * case 8: return isInfoEnabled(); + * case 7: return isConfigEnabled(); + * case 6: // fallthrough + * case 5: return isFineEnabled(); + * case 4: return isFinerEnabled(); + * case 3: return isFinestEnabled(); + * case 2: // fallthrough + * case 1: // fallthrough + * case 0: return false; + * } + * } + * } + * + * @param level A message logging level. + * @return {@code true} if the given message level is currently being logged. + */ + @Override + public abstract boolean isLoggable(Level level); + + /** + * Logs a {@link Level#SEVERE SEVERE} message. + * + * @param message The message to log. + */ + @Override + public abstract void severe(String message); + + /** + * Logs a {@link Level#WARNING WARNING} message. + * + * @param message The message to log. + */ + @Override + public abstract void warning(String message); + + /** + * Logs an {@link Level#INFO INFO} message. + * + * @param message The message to log. + */ + @Override + public abstract void info(String message); + + /** + * Logs an {@link Level#CONFIG CONFIG} message. + * + * @param message The message to log. + */ + @Override + public abstract void config(String message); + + /** + * Logs a {@link Level#FINE FINE} message. + * + * @param message The message to log. + */ + @Override + public abstract void fine(String message); + + /** + * Logs a {@link Level#FINER FINER} message. + * + * @param message The message to log. + */ + @Override + public abstract void finer(String message); + + /** + * Logs a {@link Level#FINEST FINEST} message. + * + * @param message The message to log. + */ + @Override + public abstract void finest(String message); + + /** + * Logs a method entry to the {@linkplain #getDebugLevel debug level}. Compared to the + * default {@link Logger}, this implementation bypass the level check in order to let + * the backing logging framework do its own check. + * + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of method that is being entered. + */ + @Override + public void entering(final String sourceClass, final String sourceMethod) { + logp(getDebugLevel(), sourceClass, sourceMethod, "ENTRY"); + } + + /** + * Logs a method entry to the {@linkplain #getDebugLevel debug level} with one parameter. + * Compared to the default {@link Logger}, this implementation bypass the level check in + * order to let the backing logging framework do its own check. + * + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of method that is being entered. + * @param param Parameter to the method being entered. + */ + @Override + public void entering(String sourceClass, String sourceMethod, Object param) { + logp(getDebugLevel(), sourceClass, sourceMethod, "ENTRY {0}", param); + } + + /** + * Logs a method entry to the {@linkplain #getDebugLevel debug level} with many parameters. + * Compared to the default {@link Logger}, this implementation bypass the level check in + * order to let the backing logging framework do its own check. + * + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of method that is being entered. + * @param params Array of parameters to the method being entered. + */ + @Override + public void entering(final String sourceClass, final String sourceMethod, final Object[] params) { + final String message; + if (params == null) { + message = "ENTRY"; + } else switch (params.length) { + case 0: message = "ENTRY"; break; + case 1: message = "ENTRY {0}"; break; + case 2: message = "ENTRY {0} {1}"; break; + default: { + final StringBuilder builder = new StringBuilder("ENTRY"); + for (int i=0; i{@linkplain #log(Level,String) log}(level, message). + * + * @param level One of the message level identifiers. + * @param message The message to log. + * @param thrown Throwable associated with log message. + */ + @Override + public void log(final Level level, final String message, final Throwable thrown) { + log(level, message); + } + + /** + * Logs a record at the specified level. The default implementation delegates to + * {@linkplain #log(Level,String,Object[]) log}(level, message, params) + * where the {@code params} array is built from the {@code param} object. + * + * @param level One of the message level identifiers. + * @param message The message to log. + * @param param Parameter to the method being entered. + */ + @Override + public void log(final Level level, final String message, final Object param) { + log(level, message, asArray(param)); + } + + /** + * Logs a record at the specified level. + * The defaut implementation formats the message immediately, then delegates to + * {@linkplain #log(Level,String) log}(level, message). + * + * @param level One of the message level identifiers. + * @param message The message to log. + * @param params Array of parameters to the method being entered. + */ + @Override + public void log(final Level level, final String message, final Object[] params) { + log(level, format(message, params)); + } + + /** + * Logs a record at the specified level. The defaut implementation discards + * the source class and source method, then delegates to + * {@linkplain #log(Level,String) log}(level, message). + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param message The message to log. + */ + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, + final String message) + { + log(level, message); + } + + /** + * Logs a record at the specified level. The defaut implementation discards + * the source class and source method, then delegates to + * {@linkplain #log(Level,String,Throwable) log}(level, message, thrown). + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param message The message to log. + * @param thrown Throwable associated with log message. + */ + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, + final String message, final Throwable thrown) + { + log(level, message, thrown); + } + + /** + * Logs a record at the specified level. The defaut implementation delegates to + * {@linkplain #logp(Level,String,String,String,Object[]) logp}(level, sourceClass, + * sourceMethod, message, params) where the {@code params} array is built from the + * {@code param} object. + *

+ * Note that {@code sourceClass} and {@code sourceMethod} will be discarded unless the + * target {@link #logp(Level,String,String,String) logp} method has been overridden. + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param message The message to log. + * @param param Parameter to the method being entered. + */ + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, + final String message, final Object param) + { + logp(level, sourceClass, sourceMethod, message, asArray(param)); + } + + /** + * Logs a record at the specified level. The defaut implementation formats the message + * immediately, then delegates to {@linkplain #logp(Level,String,String,String) + * logp}(level, sourceClass, sourceMethod, message). + *

+ * Note that {@code sourceClass} and {@code sourceMethod} will be discarded unless the + * target {@link #logp(Level,String,String,String) logp} method has been overridden. + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param message The message to log. + * @param params Array of parameters to the method being entered. + */ + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, + final String message, final Object[] params) + { + logp(level, sourceClass, sourceMethod, format(message, params)); + } + + /** + * Logs a localizable record at the specified level. The defaut implementation localizes the + * message immediately, then delegates to {@linkplain #logp(Level,String,String,String) + * logp}(level, sourceClass, sourceMethod, message). + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param bundleName Name of resource bundle to localize message, or {@code null}. + * @param message The message to log. + */ + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, final String message) + { + logp(level, sourceClass, sourceMethod, localize(bundleName, message)); + } + + /** + * Logs a localizable record at the specified level. The defaut implementation localizes the + * message immediately, then delegates to {@linkplain #logp(Level,String,String,String, + * Throwable) logp}(level, sourceClass, sourceMethod, message, thrown). + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param bundleName Name of resource bundle to localize message, or {@code null}. + * @param message The message to log. + * @param thrown Throwable associated with log message. + */ + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, final String message, final Throwable thrown) + { + logp(level, sourceClass, sourceMethod, localize(bundleName, message), thrown); + } + + /** + * Logs a localizable record at the specified level. The defaut implementation localizes the + * message immediately, then delegates to {@linkplain #logp(Level,String,String,String, + * Object) logp}(level, sourceClass, sourceMethod, message, param). + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param bundleName Name of resource bundle to localize message, or {@code null}. + * @param message The message to log. + * @param param Parameter to the method being entered. + */ + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, final String message, final Object param) + { + logp(level, sourceClass, sourceMethod, localize(bundleName, message), param); + } + + /** + * Logs a localizable record at the specified level. The defaut implementation localizes the + * message immediately, then delegates to {@linkplain #logp(Level,String,String,String, + * Object[]) logp}(level, sourceClass, sourceMethod, message, params). + * + * @param level One of the message level identifiers. + * @param sourceClass Name of class that issued the logging request. + * @param sourceMethod Name of the method. + * @param bundleName Name of resource bundle to localize message, or {@code null}. + * @param message The message to log. + * @param params Array of parameters to the method being entered. + */ + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, String message, final Object[] params) + { + logp(level, sourceClass, sourceMethod, localize(bundleName, message), params); + } + + /** + * Do nothing since this logger adapter does not supports handlers. + * The configuration should be fully controlled by the external logging framework + * (e.g. Commons-logging) instead, + * which is not expected to use {@link Handler} objects. + * + * @param handler A logging handler, ignored in default implementation. + */ + @Override + public void addHandler(Handler handler) { + } + + /** + * Do nothing since this logger adapter does not support handlers. + * + * @param handler A logging handler, ignored in default implementation. + */ + @Override + public void removeHandler(Handler handler) { + } + + /** + * Do nothing since this logger never use parent handlers. This is consistent + * with {@link #addHandler} not allowing to add any handlers, and avoid mixing + * loggings from the external framework with JDK loggings. + * + * @param useParentHandlers Ignored in default implementation. + */ + @Override + public void setUseParentHandlers(boolean useParentHandlers) { + } + + /** + * Do nothing since this logger adapter does not support arbitrary parents. + * More specifically, it should not inherits any configuration from a parent + * logger using the JDK logging framework. + * + * @param parent Ignored in default implementation. + */ + @Override + public void setParent(Logger parent) { + } + + /** + * Do nothing since this logger adapter does not support filters. It is difficult to query + * efficiently the filter in this {@code LoggerAdapter} architecture (e.g. we would need to + * make sure that {@link Filter#isLoggable} is invoked only once even if a {@code log} call + * is cascaded into many other {@code log} calls, and this test must works in multi-threads + * environment). + * + * @param filter Ignored in default implementation. + */ + @Override + public void setFilter(Filter filter) { + } + + /** + * Wraps the specified object in an array. This is a helper method for + * {@code log(..., Object)} methods that delegate their work to {@code log(..., Object[])} + */ + private static Object[] asArray(final Object param) { + return (param != null) ? new Object[] {param} : null; + } + + /** + * Formats the specified message. This is a helper method for + * {@code log(..., Object[])} methods that delegate their work to {@code log(...)} + */ + private static String format(String message, final Object[] params) { + if (params != null && params.length != 0) { + if (MESSAGE_FORMAT.matcher(message).find()) try { + message = MessageFormat.format(message, params); + } catch (IllegalArgumentException e) { + // The default Formatter.messageFormat implementation ignores this exception + // and uses the pattern as the message, so we mimic its behavior here. + } + } + return message; + } + + /** + * Localizes the specified message. This is a helper method for + * {@code logrb(...)} methods that delegate their work to {@code logp(...)} + */ + private static String localize(final String bundleName, String message) { + if (bundleName != null) try { + message = ResourceBundle.getBundle(bundleName).getString(message); + } catch (MissingResourceException e) { + // The default Formatter.messageFormat implementation ignores this exception + // and uses the bundle key as the message, so we mimic its behavior here. + } + return message; + } +} Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java?rev=1387950&view=auto ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java (added) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java Thu Sep 20 10:35:57 2012 @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.util.logging; + +import java.util.logging.Logger; +import net.jcip.annotations.ThreadSafe; + + +/** + * A factory for Java {@link Logger} wrapping an other logging framework. This factory is used + * only when wanting to log to an other framework than JDK logging. The {@link #getLogger(String)} + * method returns some subclass of {@link Logger} (typically {@link LoggerAdapter}) that forward + * directly all log methods to an other framework. + * + * @param The type of loggers used for the implementation backend. + * This is the type used by external frameworks like Log4J. + * + * @author Martin Desruisseaux (IRD, Geomatys) + * @since 0.3 (derived from geotk-2.4) + * @version 0.3 + * @module + * + * @see Logging + * @see LoggerAdapter + */ +@ThreadSafe +public abstract class LoggerFactory { + /** + * The logger class. We ask for this information right at construction time in order to + * force a {@link NoClassDefFoundError} early rather than only the first time a message + * is logged. + */ + private final Class loggerClass; + + /** + * Creates a new factory. + * + * @param loggerClass The class of the wrapped logger. + */ + protected LoggerFactory(final Class loggerClass) { + this.loggerClass = loggerClass; + } + + /** + * Returns the logger of the specified name, or {@code null} if the JDK logging framework + * should be used. + * + * @param name The name of the logger. + * @return The logger, or {@code null} if the JDK logging framework should be used. + */ + public Logger getLogger(final String name) { + final L target = getImplementation(name); + if (target == null) { + return null; + } + return wrap(name, target); + } + + /** + * Returns the base class of objects to be returned by {@link #getImplementation(String)}. + * The class depends on the underlying logging framework (Log4J, SLF4J, etc.). + * + * @return The type of loggers used for the implementation backend. + */ + public Class getImplementationClass() { + return loggerClass; + } + + /** + * Returns the implementation to use for the logger of the specified name. The object to be + * returned depends on the logging framework (Log4J, SLF4J, etc.). If the target + * framework redirects logging events to JDK logging, then this method shall return + * {@code null} since we should not use wrapper at all. + * + * @param name The name of the logger. + * @return The logger as an object of the target logging framework (Log4J, SLF4J, + * etc.), or {@code null} if the target framework would redirect + * to the JDK logging framework. + */ + protected abstract L getImplementation(String name); + + /** + * Wraps the specified {@linkplain #getImplementation(String) implementation} in a JDK logger. + * + * @param name The name of the logger. + * @param implementation An implementation returned by {@link #getImplementation(String)}. + * @return A new logger wrapping the specified implementation. + */ + protected abstract Logger wrap(String name, L implementation); + + /** + * Returns the {@linkplain #getImplementation(String) implementation} wrapped by the specified + * logger, or {@code null} if none. If the specified logger is not an instance of the expected + * class, then this method should returns {@code null}. + * + * @param logger The logger to test. + * @return The implementation wrapped by the specified logger, or {@code null} if none. + */ + protected abstract L unwrap(Logger logger); +} Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java?rev=1387950&view=auto ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java (added) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java Thu Sep 20 10:35:57 2012 @@ -0,0 +1,637 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.util.logging; + +import java.util.Comparator; +import java.util.ServiceLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +import org.apache.sis.util.Configuration; +import org.apache.sis.util.Static; +import org.apache.sis.util.Exceptions; +import org.apache.sis.util.Classes; + +import static java.util.Arrays.binarySearch; +import static org.apache.sis.util.Arrays.insert; + + +/** + * A set of utilities method for configuring loggings in SIS. Library implementors shall fetch + * their logger using the {@link #getLogger(Class)} static method provided in this class rather + * than {@link Logger#getLogger(String)}, in order to give SIS a chance to redirect the logging + * to an other framework like Commons-logging or + * Log4J. + *

+ * This method provides also a {@link #log(Class, String, LogRecord)} convenience static method, which + * {@linkplain LogRecord#setLoggerName set the logger name} of the given record before to log it. + * An other worthy static method is {@link #unexpectedException(Class, String, Throwable)}, for + * reporting an anomalous but nevertheless non-fatal exception. + * + * {@section Configuration} + * The log records can be redirected explicitly to an other logging framework using the + * following method call (replace {@link LoggerFactory#COMMONS_LOGGING} by + * {@link LoggerFactory#LOG4J} or an other framework if desired): + * + * {@preformat java + * Logging.SIS.setLoggerFactory(LoggerFactory.COMMONS_LOGGING); + * } + * + * Note however that the above method invocation is performed automatically if the + * {@code sis-logging-commons.jar} or the {@code sis-logging-log4j.jar} file is + * found on the classpath, so it usually doesn't need to be invoked explicitely. + * See the {@link #scanLoggerFactory()} method for more details on automatic logger + * factory registration. + * + * @author Martin Desruisseaux (Geomatys) + * @since 0.3 (derived from geotk-2.4) + * @version 0.3 + * @module + */ +public final class Logging extends Static { + /** + * Compares {@link Logging} or {@link String} objects for alphabetical order. + */ + private static final Comparator COMPARATOR = new Comparator() { + @Override 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(); + static { // Must be run after ALL assignation and before SIS (or any other Logging) creation. + ALL.scanLoggerFactory(); + } + + /** + * Logging configuration that apply only to "{@code org.apache.sis}" packages. + */ + public static final Logging SIS = getLogging("org.apache.sis"); + + /** + * The name of the base package. + */ + public final String name; + + /** + * The children {@link Logging} objects. + *

+ * 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, or {@code null} if none. If {@code null} + * (the default), then the standard JDK logging framework will be used. + * + * @see #setLoggerFactory(LoggerFactory) + */ + 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 the {@link #getLogging(String)} method instead. + */ + private Logging() { + name = ""; + } + + /** + * Creates an instance for the specified base logger. This constructor should + * not be public; use the {@link #getLogging(String)} method 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; + } + + /** + * Logs the given record to the logger associated to the given class. + * This convenience method performs the following steps: + *

+ *

    + *
  • Get the logger using {@link #getLogger(Class)};
  • + *
  • {@linkplain LogRecord#setLoggerName(String) Set the logger name} of the given record, + * if not already set;
  • + *
  • Unconditionally {@linkplain LogRecord#setSourceClassName(String) set the source class + * name} to the {@linkplain Class#getCanonicalName() canonical name} of the given class;
  • + *
  • Unconditionally {@linkplain LogRecord#setSourceMethodName(String) set the source method + * name} to the given value;
  • + *
  • {@linkplain Logger#log(LogRecord) Log} the modified record.
  • + *
+ * + * @param classe The class for which to obtain a logger. + * @param method The name of the method which is logging a record. + * @param record The record to log. + */ + public static void log(final Class classe, final String method, final LogRecord record) { + record.setSourceClassName(classe.getCanonicalName()); + record.setSourceMethodName(method); + final Logger logger = getLogger(classe); + if (record.getLoggerName() == null) { + record.setLoggerName(logger.getName()); + } + logger.log(record); + } + + /** + * 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. + */ + public static Logger getLogger(Class classe) { + Class outer; + while ((outer = classe.getEnclosingClass()) != null) { + classe = outer; + } + 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 SIS a chance to redirect logging events to + * commons-logging + * or some equivalent framework. + *

+ * If no factory was found or if the factory choose to not redirect the loggings, then this + * method returns the usual {@linkplain Logger#getLogger Logger.getLogger}(name). + * + * @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 SIS. For example methods like + * {@link #forceMonolineConsoleOutput(Level)} are invoked on a {@code Logging} instance. + *

+ * {@code Logging} instances follow the same hierarchy than {@link Logger}, i.e. + * {@code "org.apache.sis"} is the parent of {@code "org.apache.sis.referencing"}, + * {@code "org.apache.sis.metadata"}, etc. + * + * @param name The base logger name. + * @return The logging instance for the given 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 is 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 base The root logger name. + * @param create {@code true} if this method is allowed to create new {@code Logging} instance. + * @return The logging instance for the given name. + */ + private static Logging getLogging(final String base, final boolean create) { + assert Thread.holdsLock(EMPTY); + Logging logging = ALL; + if (!base.isEmpty()) { + int offset = 0; + do { + Logging[] children = logging.children; + offset = base.indexOf('.', offset); + final String name = (offset >= 0) ? base.substring(0, offset) : base; + int i = 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 = insert(children, i, 1); + children[i] = new Logging(logging, name); + logging.children = children; + } + logging = children[i]; + } while (++offset != 0); + } + return logging; + } + + /** + * Returns the logger factory, or {@code null} if none. This method returns the logger set + * by the last call to {@link #setLoggerFactory(LoggerFactory)} on this {@code Logging} + * instance or on one of its parent. + * + * @return The current logger factory. + */ + 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 {@linkplain #getLogger(String) getLogger}(name) + * when {@code name} is this {@code Logging} name or one of its children. + *

+ * If the factory is set to {@code null} (the default), then the standard Logging framework + * will be used. + * + * @param factory The new logger factory, or {@code null} if none. + */ + @Configuration + public void setLoggerFactory(final LoggerFactory factory) { + synchronized (EMPTY) { + this.factory = factory; + for (int i=0; i factory) { + assert Thread.holdsLock(EMPTY); + for (int i=0; ietc. until an acceptable factory is found. + *

+ * This method usually doesn't need to be invoked explicitly, since it is automatically + * invoked on {@code Logging} class initialization. However developers may invoke it if + * new {@code LoggerFactory}s are added later on the classpath of a running JVM. + */ + @Configuration + public void scanLoggerFactory() { + LoggerFactory factory = null; + for (final LoggerFactory found : ServiceLoader.load(LoggerFactory.class)) { + if (factory == null) { + factory = found; + } else { + factory = new DualLoggerFactory(factory, found); + } + } + setLoggerFactory(factory); + } + + /** + * Flushes all {@linkplain Handler handlers} used by the logger named {@link #name}. + * If that logger {@linkplain Logger#getUseParentHandlers() uses parent handlers}, + * then those handlers will be flushed as well. + *

+ * If the log records seem to be interleaved with the content of {@link System#out} + * or {@link System#err}, invoking this method before to write to the standard streams + * may help. + * + * @see Handler#flush() + */ + public void flush() { + for (Logger logger=getLogger(name); logger!=null; logger=logger.getParent()) { + for (final Handler handler : logger.getHandlers()) { + handler.flush(); + } + if (!logger.getUseParentHandlers()) { + break; + } + } + } + + /** + * 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.apache.sis.image"}, then this method will uses + * the first stack trace element where the fully qualified class name starts with + * {@code "org.apache.sis.image"} or {@code "org.apache.sis.image.io"}, but not + * {@code "org.apache.sis.imageio"}. + * + * @param logger Where to log the error. + * @param error The error that occurred. + * @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 optionally 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)}. + *

+ * Explicit value for class and method names are sometime preferred to automatic + * inference for the following reasons: + *

+ *

    + *
  • Automatic inference is not 100% reliable, since the Java Virtual Machine + * is free to omit stack frame in optimized code.
  • + *
  • When an exception occurred 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 developer really want to know about the private method, + * the stack trace is still available anyway.
  • + *
+ * + * @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. + * + * @see #recoverableException(Logger, Class, String, Throwable) + * @see #severeException(Logger, Class, String, Throwable) + */ + 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 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. + * + * @see #recoverableException(Class, String, Throwable) + */ + 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 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. + */ + if (logger == null) { + logger = getLogger(Logger.GLOBAL_LOGGER_NAME); + 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 JDK 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)); + String message = error.getLocalizedMessage(); + if (message != null) { + buffer.append(": ").append(message); + } + message = buffer.toString(); + message = Exceptions.formatChainedMessages(message, error); + final LogRecord record = new LogRecord(level, message); + 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(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 {@link Level#FINE FINE} level. + * + * @see #unexpectedException(Class, String, Throwable) + */ + public static boolean recoverableException(final Class classe, final String method, + final Throwable error) + { + return recoverableException(null, classe, method, error); + } + + /** + * 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 {@link Level#FINE FINE} level. + * + * @see #unexpectedException(Logger, Class, String, Throwable) + * @see #severeException(Logger, Class, String, Throwable) + */ + 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 severe error occurs. This method is similar to + * {@link #unexpectedException(Logger,Class,String,Throwable) unexpectedException} + * except that it logs the message at the {@link Level#SEVERE SEVERE} 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 {@link Level#SEVERE SEVERE} level. + * + * @see #unexpectedException(Logger, Class, String, Throwable) + * @see #recoverableException(Logger, Class, String, Throwable) + */ + public static boolean severeException(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.SEVERE); + } +} Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java?rev=1387950&view=auto ============================================================================== --- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java (added) +++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java Thu Sep 20 10:35:57 2012 @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.util.logging; + +import java.util.logging.Level; +import java.util.concurrent.TimeUnit; +import org.apache.sis.util.Configuration; + +import static org.apache.sis.util.ArgumentChecks.ensurePositive; + + +/** + * Logging levels for measurements of execution time. Different logging levels - {@link #SLOW}, + * {@link #SLOWER} and {@link #SLOWEST} - are provided in order to log only the events taking + * more than some time duration. For example the console could log only the slowest events, + * while a file could log all events considered slow. + *

+ * Every levels defined in this class have a {@linkplain #intValue() value} between the + * {@link Level#FINE} and {@link Level#CONFIG} values. Consequently performance logging are + * disabled by default, and enabling them imply enabling configuration logging too. This is + * done that way because the configuration typically have a significant impact on performance. + * + * {@section Enabling performance logging} + * Performance logging can be enabled in various ways. Among others: + *

+ *

    + *
  • The {@code $JAVA_HOME/lib/logging.properties} file can be edited in order to log + * messages at the {@code FINE} level, at least for the packages of interest.
  • + *
  • The {@link Logging#forceMonolineConsoleOutput(Level)} convenience method + * can be invoked.
  • + *
+ * + * @author Martin Desruisseaux (Geomatys) + * @since 0.3 (derived from geotk-3.16) + * @version 0.3 + * @module + */ +public final class PerformanceLevel extends Level { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 6055684381688936293L; + + /* + * IMPLEMENTATION NOTE: The level values used in the constants below are also used + * in the 'switch' statements of the 'setMinDuration(...)' method. If those values + * are modified, don't forget to update also the switch statements!! + */ + + /** + * The level for logging all time measurements, regardless of their duration. + * The {@linkplain #intValue() value} of this level is 600. + */ + public static final PerformanceLevel PERFORMANCE = new PerformanceLevel("PERFORMANCE", 600, 0); + + /** + * The level for logging relatively slow events. By default, only events having an execution + * time equals or greater than 0.1 second are logged at this level. However this threshold + * can be changed by a call to SLOW.{@linkplain #setMinDuration(long, TimeUnit)}. + */ + public static final PerformanceLevel SLOW = new PerformanceLevel("SLOW", 610, 100000000L); + + /** + * The level for logging only events slower than the ones logged at the {@link #SLOW} level. + * By default, only events having an execution time equals or greater than 1 second are + * logged at this level. However this threshold can be changed by a call to + * SLOWER.{@linkplain #setMinDuration(long, TimeUnit)}. + */ + public static final PerformanceLevel SLOWER = new PerformanceLevel("SLOWER", 620, 1000000000L); + + /** + * The level for logging only slowest events. By default, only events having an execution + * time equals or greater than 5 seconds are logged at this level. However this threshold + * can be changed by a call to SLOWEST.{@linkplain #setMinDuration(long, TimeUnit)}. + */ + public static final PerformanceLevel SLOWEST = new PerformanceLevel("SLOWEST", 630, 5000000000L); + + /** + * The minimal duration (in nanoseconds) for logging the record. + */ + private volatile long minDuration; + + /** + * Constructs a new logging level for monitoring performance. + * + * @param name The logging level name. + * @param value The level value. + * @param duration The minimal duration (in nanoseconds) for logging a record. + */ + private PerformanceLevel(final String name, final int value, final long duration) { + super(name, value); + minDuration = duration; + } + + /** + * Returns the level to use for logging an event of the given duration. + * + * @param duration The event duration. + * @param unit The unit of the given duration value. + * @return The level to use for logging an event of the given duration. + */ + public static PerformanceLevel forDuration(long duration, final TimeUnit unit) { + duration = unit.toNanos(duration); + if (duration >= SLOWER.minDuration) { + return (duration >= SLOWEST.minDuration) ? SLOWEST : SLOWER; + } else { + return (duration >= SLOW.minDuration) ? SLOW : PERFORMANCE; + } + } + + /** + * Returns the minimal duration for logging an event at this level. + * + * @param unit The unit in which to express the minimal duration. + * @return The minimal duration in the given unit. + */ + public long getMinDuration(final TimeUnit unit) { + return unit.convert(minDuration, TimeUnit.NANOSECONDS); + } + + /** + * Sets the minimal duration for logging an event at this level. Invoking this method + * may have an indirect impact of other performance levels: + *

+ *

    + *
  • If the given duration is longer than the duration of slower levels, then the later + * are also set to the given duration.
  • + *
  • If the given duration is shorter than the duration of faster levels, then the later + * are also set to the given duration.
  • + *
+ * + * {@note The duration of the PERFORMANCE level can not be modified: it is + * always zero. However invoking this method on the PERFORMANCE field will + * ensure that every SLOW* levels will have at least the given duration.} + * + * @param duration The minimal duration. + * @param unit The unit of the given duration value. + * @throws IllegalArgumentException If the given duration is negative. + */ + @Configuration + @SuppressWarnings("fallthrough") + public void setMinDuration(long duration, final TimeUnit unit) throws IllegalArgumentException { + ensurePositive("duration", duration); + duration = unit.toNanos(duration); + final int value = intValue(); + synchronized (PerformanceLevel.class) { + // Check the value of slower levels. + switch (value) { + default: throw new AssertionError(this); + case 600: if (duration > SLOW .minDuration) SLOW .minDuration = duration; + case 610: if (duration > SLOWER .minDuration) SLOWER .minDuration = duration; + case 620: if (duration > SLOWEST.minDuration) SLOWEST.minDuration = duration; + case 630: // Do nothing since there is no level slower than 'SLOWEST'. + } + // Check the value of faster levels. + switch (value) { + default: throw new AssertionError(this); + case 630: if (duration < SLOWER .minDuration) SLOWER .minDuration = duration; + case 620: if (duration < SLOW .minDuration) SLOW .minDuration = duration; + case 610: minDuration = duration; + case 600: // Do nothing, since we don't allow modification of PERFORMANCE level. + } + } + } +} Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java ------------------------------------------------------------------------------ svn:mime-type = text/plain