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 */
25package org.slf4j.bridge;
26
27import java.text.MessageFormat;
28import java.util.MissingResourceException;
29import java.util.ResourceBundle;
30import java.util.logging.Handler;
31import java.util.logging.Level;
32import java.util.logging.LogManager;
33import java.util.logging.LogRecord;
34
35import org.slf4j.Logger;
36import org.slf4j.LoggerFactory;
37import org.slf4j.spi.LocationAwareLogger;
38
39// Based on http://bugzilla.slf4j.org/show_bug.cgi?id=38
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 */
99public 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://bugzilla.slf4j.org/show_bug.cgi?id=212
263        if (params != null && params.length > 0) {
264            message = MessageFormat.format(message, params);
265        }
266        return message;
267    }
268
269    /**
270     * Publish a LogRecord.
271     * <p/>
272     * The logging request was made initially to a Logger object, which
273     * initialized the LogRecord and forwarded it here.
274     * <p/>
275     * This handler ignores the Level attached to the LogRecord, as SLF4J cares
276     * about discarding log statements.
277     *
278     * @param record Description of the log event. A null record is silently ignored
279     *               and is not published.
280     */
281    public void publish(LogRecord record) {
282        // Silently ignore null records.
283        if (record == null) {
284            return;
285        }
286
287        Logger slf4jLogger = getSLF4JLogger(record);
288        String message = record.getMessage(); // can be null!
289        // this is a check to avoid calling the underlying logging system
290        // with a null message. While it is legitimate to invoke j.u.l. with
291        // a null message, other logging frameworks do not support this.
292        // see also http://bugzilla.slf4j.org/show_bug.cgi?id=108
293        if (message == null) {
294            message = "";
295        }
296        if (slf4jLogger instanceof LocationAwareLogger) {
297            callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
298        } else {
299            callPlainSLF4JLogger(slf4jLogger, record);
300        }
301    }
302
303}
304