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