View Javadoc

    1   /**
    2    * Copyright (c) 2004-2011 QOS.ch
    3    * All rights reserved.
    4    *
    5    * Permission is hereby granted, free  of charge, to any person obtaining
    6    * a  copy  of this  software  and  associated  documentation files  (the
    7    * "Software"), to  deal in  the Software without  restriction, including
    8    * without limitation  the rights to  use, copy, modify,  merge, publish,
    9    * distribute,  sublicense, and/or sell  copies of  the Software,  and to
    10   * permit persons to whom the Software  is furnished to do so, subject to
    11   * the following conditions:
    12   *
    13   * The  above  copyright  notice  and  this permission  notice  shall  be
    14   * included in all copies or substantial portions of the Software.
    15   *
    16   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
    17   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
    18   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
    19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    21   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
    22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    23   *
    24   */
    25  package org.slf4j.bridge;
    26
    27  import java.text.MessageFormat;
    28  import java.util.MissingResourceException;
    29  import java.util.ResourceBundle;
    30  import java.util.logging.Handler;
    31  import java.util.logging.Level;
    32  import java.util.logging.LogManager;
    33  import java.util.logging.LogRecord;
    34
    35  import org.slf4j.Logger;
    36  import org.slf4j.LoggerFactory;
    37  import org.slf4j.spi.LocationAwareLogger;
    38
    39  // Based on http://jira.qos.ch/browse/SLF4J-30
    40
    41  /**
    42   * <p>Bridge/route all JUL log records to the SLF4J API.</p>
    43   * <p>Essentially, the idea is to install on the root logger an instance of
    44   * <code>SLF4JBridgeHandler</code> as the sole JUL handler in the system. Subsequently, the
    45   * SLF4JBridgeHandler instance will redirect all JUL log records are redirected
    46   * to the SLF4J API based on the following mapping of levels:
    47   * </p>
    48   * <pre>
    49   * FINEST  -&gt; TRACE
    50   * FINER   -&gt; DEBUG
    51   * FINE    -&gt; DEBUG
    52   * INFO    -&gt; INFO
    53   * WARNING -&gt; WARN
    54   * SEVERE  -&gt; ERROR</pre>
    55   * <p><b>Programmatic installation:</b></p>
    56   * <pre>
    57   * // Optionally remove existing handlers attached to j.u.l root logger
    58   * SLF4JBridgeHandler.removeHandlersForRootLogger();  // (since SLF4J 1.6.5)
    59
    60   * // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during
    61   * // the initialization phase of your application
    62   * SLF4JBridgeHandler.install();</pre>
    63   * <p><b>Installation via <em>logging.properties</em> configuration file:</b></p>
    64   * <pre>
    65   * // register SLF4JBridgeHandler as handler for the j.u.l. root logger
    66   * handlers = org.slf4j.bridge.SLF4JBridgeHandler</pre>
    67   * <p>Once SLF4JBridgeHandler is installed, logging by j.u.l. loggers will be directed to
    68   * SLF4J. Example: </p>
    69   * <pre>
    70   * import  java.util.logging.Logger;
    71   * ...
    72   * // usual pattern: get a Logger and then log a message
    73   * Logger julLogger = Logger.getLogger(&quot;org.wombat&quot;);
    74   * julLogger.fine(&quot;hello world&quot;); // this will get redirected to SLF4J</pre>
    75   *
    76   * <p>Please note that translating a java.util.logging event into SLF4J incurs the
    77   * cost of constructing {@link LogRecord} instance regardless of whether the
    78   * SLF4J logger is disabled for the given level. <b>Consequently, j.u.l. to
    79   * SLF4J translation can seriously increase the cost of disabled logging
    80   * statements (60 fold or 6000% increase) and measurably impact the performance of enabled log
    81   * statements (20% overall increase).</b> Please note that as of logback-version 0.9.25,
    82   * it is possible to completely eliminate the 60 fold translation overhead for disabled
    83   * log statements with the help of <a href="http://logback.qos.ch/manual/configuration.html#LevelChangePropagator">LevelChangePropagator</a>.
    84   * </p>
    85   *
    86   * <p>If you are concerned about application performance, then use of <code>SLF4JBridgeHandler</code>
    87   * is appropriate only if any one the following two conditions is true:</p>
    88   * <ol>
    89   * <li>few j.u.l. logging statements are in play</li>
    90   * <li>LevelChangePropagator has been installed</li>
    91   * </ol>
    92   *
    93   * @author Christian Stein
    94   * @author Joern Huxhorn
    95   * @author Ceki G&uuml;lc&uuml;
    96   * @author Darryl Smith
    97   * @since 1.5.1
    98   */
    99  public class SLF4JBridgeHandler extends Handler {
    100
    101     // The caller is java.util.logging.Logger
    102     private static final String FQCN = java.util.logging.Logger.class.getName();
    103     private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
    104
    105     private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
    106     private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
    107     private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
    108     private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
    109
    110     /**
    111      * Adds a SLF4JBridgeHandler instance to jul's root logger.
    112      * <p/>
    113      * <p/>
    114      * This handler will redirect j.u.l. logging to SLF4J. However, only logs enabled
    115      * in j.u.l. will be redirected. For example, if a log statement invoking a
    116      * j.u.l. logger is disabled, then the corresponding non-event will <em>not</em>
    117      * reach SLF4JBridgeHandler and cannot be redirected.
    118      */
    119     public static void install() {
    120         LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());
    121     }
    122
    123     private static java.util.logging.Logger getRootLogger() {
    124         return LogManager.getLogManager().getLogger("");
    125     }
    126
    127     /**
    128      * Removes previously installed SLF4JBridgeHandler instances. See also
    129      * {@link #install()}.
    130      *
    131      * @throws SecurityException A <code>SecurityException</code> is thrown, if a security manager
    132      *                           exists and if the caller does not have
    133      *                           LoggingPermission("control").
    134      */
    135     public static void uninstall() throws SecurityException {
    136         java.util.logging.Logger rootLogger = getRootLogger();
    137         Handler[] handlers = rootLogger.getHandlers();
    138         for (int i = 0; i < handlers.length; i++) {
    139             if (handlers[i] instanceof SLF4JBridgeHandler) {
    140                 rootLogger.removeHandler(handlers[i]);
    141             }
    142         }
    143     }
    144
    145     /**
    146      * Returns true if SLF4JBridgeHandler has been previously installed, returns false otherwise.
    147      *
    148      * @return true if SLF4JBridgeHandler is already installed, false other wise
    149      * @throws SecurityException
    150      */
    151     public static boolean isInstalled() throws SecurityException {
    152         java.util.logging.Logger rootLogger = getRootLogger();
    153         Handler[] handlers = rootLogger.getHandlers();
    154         for (int i = 0; i < handlers.length; i++) {
    155             if (handlers[i] instanceof SLF4JBridgeHandler) {
    156                 return true;
    157             }
    158         }
    159         return false;
    160     }
    161
    162     /**
    163      * Invoking this method removes/unregisters/detaches all handlers currently attached to the root logger
    164      * @since 1.6.5
    165      */
    166     public static void removeHandlersForRootLogger() {
    167         java.util.logging.Logger rootLogger = getRootLogger();
    168         java.util.logging.Handler[] handlers = rootLogger.getHandlers();
    169         for (int i = 0; i < handlers.length; i++) {
    170             rootLogger.removeHandler(handlers[i]);
    171         }
    172     }
    173
    174     /**
    175      * Initialize this handler.
    176      */
    177     public SLF4JBridgeHandler() {
    178     }
    179
    180     /**
    181      * No-op implementation.
    182      */
    183     public void close() {
    184         // empty
    185     }
    186
    187     /**
    188      * No-op implementation.
    189      */
    190     public void flush() {
    191         // empty
    192     }
    193
    194     /**
    195      * Return the Logger instance that will be used for logging.
    196      */
    197     protected Logger getSLF4JLogger(LogRecord record) {
    198         String name = record.getLoggerName();
    199         if (name == null) {
    200             name = UNKNOWN_LOGGER_NAME;
    201         }
    202         return LoggerFactory.getLogger(name);
    203     }
    204
    205     protected void callLocationAwareLogger(LocationAwareLogger lal, LogRecord record) {
    206         int julLevelValue = record.getLevel().intValue();
    207         int slf4jLevel;
    208
    209         if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
    210             slf4jLevel = LocationAwareLogger.TRACE_INT;
    211         } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
    212             slf4jLevel = LocationAwareLogger.DEBUG_INT;
    213         } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
    214             slf4jLevel = LocationAwareLogger.INFO_INT;
    215         } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
    216             slf4jLevel = LocationAwareLogger.WARN_INT;
    217         } else {
    218             slf4jLevel = LocationAwareLogger.ERROR_INT;
    219         }
    220         String i18nMessage = getMessageI18N(record);
    221         lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown());
    222     }
    223
    224     protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
    225         String i18nMessage = getMessageI18N(record);
    226         int julLevelValue = record.getLevel().intValue();
    227         if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
    228             slf4jLogger.trace(i18nMessage, record.getThrown());
    229         } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
    230             slf4jLogger.debug(i18nMessage, record.getThrown());
    231         } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
    232             slf4jLogger.info(i18nMessage, record.getThrown());
    233         } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
    234             slf4jLogger.warn(i18nMessage, record.getThrown());
    235         } else {
    236             slf4jLogger.error(i18nMessage, record.getThrown());
    237         }
    238     }
    239
    240     /**
    241      * Get the record's message, possibly via a resource bundle.
    242      *
    243      * @param record
    244      * @return
    245      */
    246     private String getMessageI18N(LogRecord record) {
    247         String message = record.getMessage();
    248
    249         if (message == null) {
    250             return null;
    251         }
    252
    253         ResourceBundle bundle = record.getResourceBundle();
    254         if (bundle != null) {
    255             try {
    256                 message = bundle.getString(message);
    257             } catch (MissingResourceException e) {
    258             }
    259         }
    260         Object[] params = record.getParameters();
    261         // avoid formatting when there are no or 0 parameters. see also
    262         // http://jira.qos.ch/browse/SLF4J-203
    263         if (params != null && params.length > 0) {
    264             try {
    265                 message = MessageFormat.format(message, params);
    266             } catch (IllegalArgumentException e) {
    267                 // default to the same behavior as in java.util.logging.Formatter.formatMessage(LogRecord)
    268                 // see also http://jira.qos.ch/browse/SLF4J-337
    269                 return message;
    270             }
    271         }
    272         return message;
    273     }
    274
    275     /**
    276      * Publish a LogRecord.
    277      * <p/>
    278      * The logging request was made initially to a Logger object, which
    279      * initialized the LogRecord and forwarded it here.
    280      * <p/>
    281      * This handler ignores the Level attached to the LogRecord, as SLF4J cares
    282      * about discarding log statements.
    283      *
    284      * @param record Description of the log event. A null record is silently ignored
    285      *               and is not published.
    286      */
    287     public void publish(LogRecord record) {
    288         // Silently ignore null records.
    289         if (record == null) {
    290             return;
    291         }
    292
    293         Logger slf4jLogger = getSLF4JLogger(record);
    294         String message = record.getMessage(); // can be null!
    295         // this is a check to avoid calling the underlying logging system
    296         // with a null message. While it is legitimate to invoke j.u.l. with
    297         // a null message, other logging frameworks do not support this.
    298         // see also http://jira.qos.ch/browse/SLF4J-99
    299         if (message == null) {
    300             message = "";
    301         }
    302         if (slf4jLogger instanceof LocationAwareLogger) {
    303             callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
    304         } else {
    305             callPlainSLF4JLogger(slf4jLogger, record);
    306         }
    307     }
    308
    309 }