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      * Returns true if SLF4JBridgeHandler has been previously installed, returns false otherwise.
145      * @return
146      * @throws SecurityException
147      */
148     public static boolean isInstalled() throws SecurityException {
149         java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(
150             "");
151         Handler[] handlers = rootLogger.getHandlers();
152         for (int i = 0; i < handlers.length; i++) {
153           if (handlers[i] instanceof SLF4JBridgeHandler) {
154             return true;
155           }
156         }
157         return false;
158       }
159 
160 
161   /**
162    * Initialize this handler.
163    * 
164    */
165   public SLF4JBridgeHandler() {
166   }
167 
168   /**
169    * No-op implementation.
170    */
171   public void close() {
172     // empty
173   }
174 
175   /**
176    * No-op implementation.
177    */
178   public void flush() {
179     // empty
180   }
181 
182   /**
183    * Return the Logger instance that will be used for logging.
184    */
185   protected Logger getSLF4JLogger(LogRecord record) {
186     String name = record.getLoggerName();
187     if (name == null) {
188       name = UNKNOWN_LOGGER_NAME;
189     }
190     return LoggerFactory.getLogger(name);
191   }
192 
193   protected void callLocationAwareLogger(LocationAwareLogger lal,
194       LogRecord record) {
195     int julLevelValue = record.getLevel().intValue();
196     int slf4jLevel;
197 
198     if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
199       slf4jLevel = LocationAwareLogger.TRACE_INT;
200     } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
201       slf4jLevel = LocationAwareLogger.DEBUG_INT;
202     } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
203       slf4jLevel = LocationAwareLogger.INFO_INT;
204     } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
205       slf4jLevel = LocationAwareLogger.WARN_INT;
206     } else {
207       slf4jLevel = LocationAwareLogger.ERROR_INT;
208     }
209     String i18nMessage = getMessageI18N(record);
210     lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown());
211   }
212 
213   protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
214     String i18nMessage = getMessageI18N(record);
215     int julLevelValue = record.getLevel().intValue();
216     if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
217       slf4jLogger.trace(i18nMessage, record.getThrown());
218     } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
219       slf4jLogger.debug(i18nMessage, record.getThrown());
220     } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
221       slf4jLogger.info(i18nMessage, record.getThrown());
222     } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
223       slf4jLogger.warn(i18nMessage, record.getThrown());
224     } else {
225       slf4jLogger.error(i18nMessage, record.getThrown());
226     }
227   }
228 
229   /**
230    * Get the record's message, possibly via a resource bundle.
231    * 
232    * @param record
233    * @return
234    */
235   private String getMessageI18N(LogRecord record) {
236     String message = record.getMessage();
237 
238     if (message == null) {
239       return null;
240     }
241 
242     ResourceBundle bundle = record.getResourceBundle();
243     if (bundle != null) {
244       try {
245         message = bundle.getString(message);
246       } catch (MissingResourceException e) {
247       }
248     }
249     Object[] params = record.getParameters();
250     if (params != null) {
251       message = MessageFormat.format(message, params);
252     }
253     return message;
254   }
255 
256   /**
257    * Publish a LogRecord.
258    * <p>
259    * The logging request was made initially to a Logger object, which
260    * initialized the LogRecord and forwarded it here.
261    * <p>
262    * This handler ignores the Level attached to the LogRecord, as SLF4J cares
263    * about discarding log statements.
264    * 
265    * @param record
266    *          Description of the log event. A null record is silently ignored
267    *          and is not published.
268    */
269   public void publish(LogRecord record) {
270     // Silently ignore null records.
271     if (record == null) {
272       return;
273     }
274 
275     Logger slf4jLogger = getSLF4JLogger(record);
276     String message = record.getMessage(); // can be null!
277     // this is a check to avoid calling the underlying logging system
278     // with a null message. While it is legitimate to invoke j.u.l. with
279     // a null message, other logging frameworks do not support this.
280     // see also http://bugzilla.slf4j.org/show_bug.cgi?id=108
281     if (message == null) {
282       message = "";
283     }
284     if (slf4jLogger instanceof LocationAwareLogger) {
285       callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
286     } else {
287       callPlainSLF4JLogger(slf4jLogger, record);
288     }
289   }
290 
291 }