1   /*
2    * Copyright (c) 2004-2008 QOS.ch
3    *
4    * All rights reserved.
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining
7    * a copy of this software and associated documentation files (the
8    * "Software"), to  deal in  the Software without  restriction, including
9    * without limitation  the rights to  use, copy, modify,  merge, publish,
10   * distribute, and/or sell copies of  the Software, and to permit persons
11   * to whom  the Software is furnished  to do so, provided  that the above
12   * copyright notice(s) and this permission notice appear in all copies of
13   * the  Software and  that both  the above  copyright notice(s)  and this
14   * permission notice appear in supporting documentation.
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 NONINFRINGEMENT
19   * OF  THIRD PARTY  RIGHTS. IN  NO EVENT  SHALL THE  COPYRIGHT  HOLDER OR
20   * HOLDERS  INCLUDED IN  THIS  NOTICE BE  LIABLE  FOR ANY  CLAIM, OR  ANY
21   * SPECIAL INDIRECT  OR CONSEQUENTIAL DAMAGES, OR  ANY DAMAGES WHATSOEVER
22   * RESULTING FROM LOSS  OF USE, DATA OR PROFITS, WHETHER  IN AN ACTION OF
23   * CONTRACT, NEGLIGENCE  OR OTHER TORTIOUS  ACTION, ARISING OUT OF  OR IN
24   * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25   *
26   * Except as  contained in  this notice, the  name of a  copyright holder
27   * shall not be used in advertising or otherwise to promote the sale, use
28   * or other dealings in this Software without prior written authorization
29   * of the copyright holder.
30   */
31  
32  package org.slf4j.bridge;
33  
34  import java.text.MessageFormat;
35  import java.util.MissingResourceException;
36  import java.util.ResourceBundle;
37  import java.util.logging.Handler;
38  import java.util.logging.Level;
39  import java.util.logging.LogManager;
40  import java.util.logging.LogRecord;
41  
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  import org.slf4j.spi.LocationAwareLogger;
45  
46  // Based on http://bugzilla.slf4j.org/show_bug.cgi?id=38
47  
48  /**
49   * Bridge/route all JUL log records to the SLF4J API.
50   * 
51   * <p>
52   * Essentially, the idea is to install on the root logger an instance of
53   * SLF4JBridgeHandler as the sole JUL handler in the system. Subsequently, the
54   * SLF4JBridgeHandler instance will redirect all JUL log records are redirected
55   * to the SLF4J API based on the following mapping of levels:
56   * 
57   * <pre>
58   * FINEST  -&gt; TRACE
59   * FINER   -&gt; DEBUG
60   * FINE    -&gt; DEBUG
61   * INFO    -&gt; INFO
62   * WARNING -&gt; WARN
63   * SEVER   -&gt; ERROR
64   * </pre>
65   * 
66   * Usage:
67   * 
68   * <pre>
69   * // call only once during initialization time of your application
70   * SLF4JBridgeHandler.install();
71   * 
72   * // usual pattern: get a Logger and then log a message
73   * java.util.logging.Logger julLogger = java.util.logging.Logger
74   *     .getLogger(&quot;org.wombat&quot;);
75   * julLogger.fine(&quot;hello world&quot;); // this will get redirected to SLF4J
76   * </pre>
77   * 
78   * <p>
79   * Please note that translating a java.util.logging event into SLF4J incurs the
80   * cost of constructing {@link LogRecord} instance regardless of whether the
81   * SLF4J logger is disabled for the given level. <b>Consequently, j.u.l. to
82   * SLF4J translation can seriously impact on the cost of disabled logging
83   * statements (60 fold increase) and a measurable impact on enabled log
84   * statements (20% overall increase). </b>
85   * </p>
86   * 
87   * <p>
88   * If application performance is a concern, then use of SLF4JBridgeHandler is
89   * appropriate only if few j.u.l. logging statements are in play.
90   * 
91   * @author Christian Stein
92   * @author Joern Huxhorn
93   * @author Ceki G&uuml;lc&uuml;
94   * @author Darryl Smith
95   * 
96   * @since 1.5.1
97   */
98  public class SLF4JBridgeHandler extends Handler {
99  
100   // The caller is java.util.logging.Logger
101   private static final String FQCN = java.util.logging.Logger.class.getName();
102   private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
103 
104   private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
105   private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
106   private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
107   private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
108 
109   /**
110    * Adds a SLF4JBridgeHandler instance to jul's root logger.
111    * 
112    * <p>
113    * This handler will redirect jul logging to SLF4J. However, only logs enabled
114    * in j.u.l. will be redirected. For example, if a log statement invoking a
115    * j.u.l. logger disabled that statement, by definition, will <em>not</em>
116    * reach any SLF4JBridgeHandler instance and cannot be redirected.
117    */
118   public static void install() {
119     LogManager.getLogManager().getLogger("").addHandler(
120         new SLF4JBridgeHandler());
121   }
122 
123   /**
124    * Removes previously installed SLF4JBridgeHandler instances. See also
125    * {@link #install()}.
126    * 
127    * @throws SecurityException
128    *           A <code>SecurityException</code> is thrown, if a security manager
129    *           exists and if the caller does not have
130    *           LoggingPermission("control").
131    */
132   public static void uninstall() throws SecurityException {
133     java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(
134         "");
135     Handler[] handlers = rootLogger.getHandlers();
136     for (int i = 0; i < handlers.length; i++) {
137       if (handlers[i] instanceof SLF4JBridgeHandler) {
138         rootLogger.removeHandler(handlers[i]);
139       }
140     }
141   }
142 
143   /**
144    * Initialize this handler.
145    * 
146    */
147   public SLF4JBridgeHandler() {
148   }
149 
150   /**
151    * No-op implementation.
152    */
153   public void close() {
154     // empty
155   }
156 
157   /**
158    * No-op implementation.
159    */
160   public void flush() {
161     // empty
162   }
163 
164   /**
165    * Return the Logger instance that will be used for logging.
166    */
167   protected Logger getSLF4JLogger(LogRecord record) {
168     String name = record.getLoggerName();
169     if (name == null) {
170       name = UNKNOWN_LOGGER_NAME;
171     }
172     return LoggerFactory.getLogger(name);
173   }
174 
175   protected void callLocationAwareLogger(LocationAwareLogger lal,
176       LogRecord record) {
177     int julLevelValue = record.getLevel().intValue();
178     int slf4jLevel;
179 
180     if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
181       slf4jLevel = LocationAwareLogger.TRACE_INT;
182     } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
183       slf4jLevel = LocationAwareLogger.DEBUG_INT;
184     } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
185       slf4jLevel = LocationAwareLogger.INFO_INT;
186     } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
187       slf4jLevel = LocationAwareLogger.WARN_INT;
188     } else {
189       slf4jLevel = LocationAwareLogger.ERROR_INT;
190     }
191     String i18nMessage = getMessageI18N(record);
192     lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown());
193   }
194 
195   protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
196     String i18nMessage = getMessageI18N(record);
197     int julLevelValue = record.getLevel().intValue();
198     if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
199       slf4jLogger.trace(i18nMessage, record.getThrown());
200     } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
201       slf4jLogger.debug(i18nMessage, record.getThrown());
202     } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
203       slf4jLogger.info(i18nMessage, record.getThrown());
204     } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
205       slf4jLogger.warn(i18nMessage, record.getThrown());
206     } else {
207       slf4jLogger.error(i18nMessage, record.getThrown());
208     }
209   }
210 
211   /**
212    * Get the record's message, possibly via a resource bundle.
213    * 
214    * @param record
215    * @return
216    */
217   private String getMessageI18N(LogRecord record) {
218     String message = record.getMessage();
219 
220     if (message == null) {
221       return null;
222     }
223 
224     ResourceBundle bundle = record.getResourceBundle();
225     if (bundle != null) {
226       try {
227         message = bundle.getString(message);
228       } catch (MissingResourceException e) {
229       }
230     }
231     Object[] params = record.getParameters();
232     if (params != null) {
233       message = MessageFormat.format(message, params);
234     }
235     return message;
236   }
237 
238   /**
239    * Publish a LogRecord.
240    * <p>
241    * The logging request was made initially to a Logger object, which
242    * initialized the LogRecord and forwarded it here.
243    * <p>
244    * This handler ignores the Level attached to the LogRecord, as SLF4J cares
245    * about discarding log statements.
246    * 
247    * @param record
248    *          Description of the log event. A null record is silently ignored
249    *          and is not published.
250    */
251   public void publish(LogRecord record) {
252     // Silently ignore null records.
253     if (record == null) {
254       return;
255     }
256 
257     Logger slf4jLogger = getSLF4JLogger(record);
258     String message = record.getMessage(); // can be null!
259     // this is a check to avoid calling the underlying logging system
260     // with a null message. While it is legitimate to invoke j.u.l. with
261     // a null message, other logging frameworks do not support this.
262     // see also http://bugzilla.slf4j.org/show_bug.cgi?id=108
263     if (message == null) {
264       message = "";
265     }
266     if (slf4jLogger instanceof LocationAwareLogger) {
267       callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
268     } else {
269       callPlainSLF4JLogger(slf4jLogger, record);
270     }
271   }
272 
273 }