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 * <h2>As a Java 9/Jigsaw module</h2>
094 * 
095 * <p>Given that <b>to</b> is a reserved keyword under Java 9 within module productions, 
096 * the MAFIFEST.MF file in <em>jul-to-slf4j.jar</em> declares <b>jul_to_slf4j</b> as 
097 * its Automatic Module Name. Thus, if your application is Jigsaw modularized, the requires 
098 * statement in your <em>module-info.java</em> needs to be <b>jul_to_slf4j</b> 
099 * (note the two underscores).
100 *
101 * 
102 * @author Christian Stein
103 * @author Joern Huxhorn
104 * @author Ceki G&uuml;lc&uuml;
105 * @author Darryl Smith
106 * @since 1.5.1
107 */
108public class SLF4JBridgeHandler extends Handler {
109
110    // The caller is java.util.logging.Logger
111    private static final String FQCN = java.util.logging.Logger.class.getName();
112    private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
113
114    private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
115    private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
116    private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
117    private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
118
119    /**
120     * Adds a SLF4JBridgeHandler instance to jul's root logger.
121     * <p/>
122     * <p/>
123     * This handler will redirect j.u.l. logging to SLF4J. However, only logs enabled
124     * in j.u.l. will be redirected. For example, if a log statement invoking a
125     * j.u.l. logger is disabled, then the corresponding non-event will <em>not</em>
126     * reach SLF4JBridgeHandler and cannot be redirected.
127     */
128    public static void install() {
129        LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());
130    }
131
132    private static java.util.logging.Logger getRootLogger() {
133        return LogManager.getLogManager().getLogger("");
134    }
135
136    /**
137     * Removes previously installed SLF4JBridgeHandler instances. See also
138     * {@link #install()}.
139     *
140     * @throws SecurityException A <code>SecurityException</code> is thrown, if a security manager
141     *                           exists and if the caller does not have
142     *                           LoggingPermission("control").
143     */
144    public static void uninstall() throws SecurityException {
145        java.util.logging.Logger rootLogger = getRootLogger();
146        Handler[] handlers = rootLogger.getHandlers();
147        for (int i = 0; i < handlers.length; i++) {
148            if (handlers[i] instanceof SLF4JBridgeHandler) {
149                rootLogger.removeHandler(handlers[i]);
150            }
151        }
152    }
153
154    /**
155     * Returns true if SLF4JBridgeHandler has been previously installed, returns false otherwise.
156     *
157     * @return true if SLF4JBridgeHandler is already installed, false other wise
158     * @throws SecurityException
159     */
160    public static boolean isInstalled() throws SecurityException {
161        java.util.logging.Logger rootLogger = getRootLogger();
162        Handler[] handlers = rootLogger.getHandlers();
163        for (int i = 0; i < handlers.length; i++) {
164            if (handlers[i] instanceof SLF4JBridgeHandler) {
165                return true;
166            }
167        }
168        return false;
169    }
170
171    /**
172     * Invoking this method removes/unregisters/detaches all handlers currently attached to the root logger
173     * @since 1.6.5
174     */
175    public static void removeHandlersForRootLogger() {
176        java.util.logging.Logger rootLogger = getRootLogger();
177        java.util.logging.Handler[] handlers = rootLogger.getHandlers();
178        for (int i = 0; i < handlers.length; i++) {
179            rootLogger.removeHandler(handlers[i]);
180        }
181    }
182
183    /**
184     * Initialize this handler.
185     */
186    public SLF4JBridgeHandler() {
187    }
188
189    /**
190     * No-op implementation.
191     */
192    public void close() {
193        // empty
194    }
195
196    /**
197     * No-op implementation.
198     */
199    public void flush() {
200        // empty
201    }
202
203    /**
204     * Return the Logger instance that will be used for logging.
205     */
206    protected Logger getSLF4JLogger(LogRecord record) {
207        String name = record.getLoggerName();
208        if (name == null) {
209            name = UNKNOWN_LOGGER_NAME;
210        }
211        return LoggerFactory.getLogger(name);
212    }
213
214    protected void callLocationAwareLogger(LocationAwareLogger lal, LogRecord record) {
215        int julLevelValue = record.getLevel().intValue();
216        int slf4jLevel;
217
218        if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
219            slf4jLevel = LocationAwareLogger.TRACE_INT;
220        } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
221            slf4jLevel = LocationAwareLogger.DEBUG_INT;
222        } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
223            slf4jLevel = LocationAwareLogger.INFO_INT;
224        } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
225            slf4jLevel = LocationAwareLogger.WARN_INT;
226        } else {
227            slf4jLevel = LocationAwareLogger.ERROR_INT;
228        }
229        String i18nMessage = getMessageI18N(record);
230        lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown());
231    }
232
233    protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
234        String i18nMessage = getMessageI18N(record);
235        int julLevelValue = record.getLevel().intValue();
236        if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
237            slf4jLogger.trace(i18nMessage, record.getThrown());
238        } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
239            slf4jLogger.debug(i18nMessage, record.getThrown());
240        } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
241            slf4jLogger.info(i18nMessage, record.getThrown());
242        } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
243            slf4jLogger.warn(i18nMessage, record.getThrown());
244        } else {
245            slf4jLogger.error(i18nMessage, record.getThrown());
246        }
247    }
248
249    /**
250     * Get the record's message, possibly via a resource bundle.
251     *
252     * @param record
253     * @return
254     */
255    private String getMessageI18N(LogRecord record) {
256        String message = record.getMessage();
257
258        if (message == null) {
259            return null;
260        }
261
262        ResourceBundle bundle = record.getResourceBundle();
263        if (bundle != null) {
264            try {
265                message = bundle.getString(message);
266            } catch (MissingResourceException e) {
267            }
268        }
269        Object[] params = record.getParameters();
270        // avoid formatting when there are no or 0 parameters. see also
271        // http://jira.qos.ch/browse/SLF4J-203
272        if (params != null && params.length > 0) {
273            try {
274                message = MessageFormat.format(message, params);
275            } catch (IllegalArgumentException e) {
276                // default to the same behavior as in java.util.logging.Formatter.formatMessage(LogRecord)
277                // see also http://jira.qos.ch/browse/SLF4J-337
278                return message;
279            }
280        }
281        return message;
282    }
283
284    /**
285     * Publish a LogRecord.
286     * <p/>
287     * The logging request was made initially to a Logger object, which
288     * initialized the LogRecord and forwarded it here.
289     * <p/>
290     * This handler ignores the Level attached to the LogRecord, as SLF4J cares
291     * about discarding log statements.
292     *
293     * @param record Description of the log event. A null record is silently ignored
294     *               and is not published.
295     */
296    public void publish(LogRecord record) {
297        // Silently ignore null records.
298        if (record == null) {
299            return;
300        }
301
302        Logger slf4jLogger = getSLF4JLogger(record);
303        String message = record.getMessage(); // can be null!
304        // this is a check to avoid calling the underlying logging system
305        // with a null message. While it is legitimate to invoke j.u.l. with
306        // a null message, other logging frameworks do not support this.
307        // see also http://jira.qos.ch/browse/SLF4J-99
308        if (message == null) {
309            message = "";
310        }
311        if (slf4jLogger instanceof LocationAwareLogger) {
312            callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
313        } else {
314            callPlainSLF4JLogger(slf4jLogger, record);
315        }
316    }
317
318}