    001/**
    002 * Copyright (c) 2004-2011 QOS.ch
    003 * All rights reserved.
    004 *
    005 * Permission is hereby granted, free  of charge, to any person obtaining
    006 * a  copy  of this  software  and  associated  documentation files  (the
    007 * "Software"), to  deal in  the Software without  restriction, including
    008 * without limitation  the rights to  use, copy, modify,  merge, publish,
    009 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
    010 * permit persons to whom the Software  is furnished to do so, subject to
    011 * the following conditions:
    012 *
    013 * The  above  copyright  notice  and  this permission  notice  shall  be
    014 * included in all copies or substantial portions of the Software.
    015 *
    016 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
    017 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
    018 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
    019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    021 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
    022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    023 *
    024 */
    025package org.slf4j.bridge;
    026
    027import java.text.MessageFormat;
    028import java.util.MissingResourceException;
    029import java.util.ResourceBundle;
    030import java.util.logging.Handler;
    031import java.util.logging.Level;
    032import java.util.logging.LogManager;
    033import java.util.logging.LogRecord;
    034
    035import org.slf4j.Logger;
    036import org.slf4j.LoggerFactory;
    037import org.slf4j.spi.LocationAwareLogger;
    038
    039// Based on http://jira.qos.ch/browse/SLF4J-30
    040
    041/**
    042 * <p>Bridge/route all JUL log records to the SLF4J API.</p>
    043 * <p>Essentially, the idea is to install on the root logger an instance of
    044 * <code>SLF4JBridgeHandler</code> as the sole JUL handler in the system. Subsequently, the
    045 * SLF4JBridgeHandler instance will redirect all JUL log records are redirected
    046 * to the SLF4J API based on the following mapping of levels:
    047 * </p>
    048 * <pre>
    049 * FINEST  -&gt; TRACE
    050 * FINER   -&gt; DEBUG
    051 * FINE    -&gt; DEBUG
    052 * INFO    -&gt; INFO
    053 * WARNING -&gt; WARN
    054 * SEVERE  -&gt; ERROR</pre>
    055 * <p><b>Programmatic installation:</b></p>
    056 * <pre>
    057 * // Optionally remove existing handlers attached to j.u.l root logger
    058 * SLF4JBridgeHandler.removeHandlersForRootLogger();  // (since SLF4J 1.6.5)
    059
    060 * // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during
    061 * // the initialization phase of your application
    062 * SLF4JBridgeHandler.install();</pre>
    063 * <p><b>Installation via <em>logging.properties</em> configuration file:</b></p>
    064 * <pre>
    065 * // register SLF4JBridgeHandler as handler for the j.u.l. root logger
    066 * handlers = org.slf4j.bridge.SLF4JBridgeHandler</pre>
    067 * <p>Once SLF4JBridgeHandler is installed, logging by j.u.l. loggers will be directed to
    068 * SLF4J. Example: </p>
    069 * <pre>
    070 * import  java.util.logging.Logger;
    071 * ...
    072 * // usual pattern: get a Logger and then log a message
    073 * Logger julLogger = Logger.getLogger(&quot;org.wombat&quot;);
    074 * julLogger.fine(&quot;hello world&quot;); // this will get redirected to SLF4J</pre>
    075 *
    076 * <p>Please note that translating a java.util.logging event into SLF4J incurs the
    077 * cost of constructing {@link LogRecord} instance regardless of whether the
    078 * SLF4J logger is disabled for the given level. <b>Consequently, j.u.l. to
    079 * SLF4J translation can seriously increase the cost of disabled logging
    080 * statements (60 fold or 6000% increase) and measurably impact the performance of enabled log
    081 * statements (20% overall increase).</b> Please note that as of logback-version 0.9.25,
    082 * it is possible to completely eliminate the 60 fold translation overhead for disabled
    083 * log statements with the help of <a href="http://logback.qos.ch/manual/configuration.html#LevelChangePropagator">LevelChangePropagator</a>.
    084 * </p>
    085 *
    086 * <p>If you are concerned about application performance, then use of <code>SLF4JBridgeHandler</code>
    087 * is appropriate only if any one the following two conditions is true:</p>
    088 * <ol>
    089 * <li>few j.u.l. logging statements are in play</li>
    090 * <li>LevelChangePropagator has been installed</li>
    091 * </ol>
    092 *
    093 * @author Christian Stein
    094 * @author Joern Huxhorn
    095 * @author Ceki G&uuml;lc&uuml;
    096 * @author Darryl Smith
    097 * @since 1.5.1
    098 */
    099public 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}