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   * @author Christian Stein
79   * @author Joern Huxhorn
80   * @author Ceki G&uuml;lc&uuml;
81   * @author Darryl Smith
82   * 
83   * @since 1.5.1
84   */
85  public class SLF4JBridgeHandler extends Handler {
86  
87    // The caller is java.util.logging.Logger
88    private static final String FQCN = java.util.logging.Logger.class.getName();
89    private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
90  
91    private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
92    private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
93    private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
94    private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
95  
96    /**
97     * Adds a SLF4JBridgeHandler instance to jul's root logger.
98     * 
99     * <p>
100    * This handler will redirect jul logging to SLF4J. However, only logs enabled
101    * in j.u.l. will be redirected. For example, if a log statement invoking a
102    * j.u.l. logger disabled that statement, by definition, will <em>not</em>
103    * reach any SLF4JBridgeHandler instance and cannot be redirected.
104    */
105   public static void install() {
106     LogManager.getLogManager().getLogger("").addHandler(
107         new SLF4JBridgeHandler());
108   }
109 
110   /**
111    * Removes previously installed SLF4JBridgeHandler instances. See also
112    * {@link #install()}.
113    * 
114    * @throws SecurityException
115    *           A <code>SecurityException</code> is thrown, if a security manager
116    *           exists and if the caller does not have
117    *           LoggingPermission("control").
118    */
119   public static void uninstall() throws SecurityException {
120     java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(
121         "");
122     Handler[] handlers = rootLogger.getHandlers();
123     for (int i = 0; i < handlers.length; i++) {
124       if (handlers[i] instanceof SLF4JBridgeHandler) {
125         rootLogger.removeHandler(handlers[i]);
126       }
127     }
128   }
129 
130   /**
131    * Initialize this handler.
132    * 
133    */
134   public SLF4JBridgeHandler() {
135   }
136 
137   /**
138    * No-op implementation.
139    */
140   public void close() {
141     // empty
142   }
143 
144   /**
145    * No-op implementation.
146    */
147   public void flush() {
148     // empty
149   }
150 
151   /**
152    * Return the Logger instance that will be used for logging.
153    */
154   protected Logger getSLF4JLogger(LogRecord record) {
155     String name = record.getLoggerName();
156     if (name == null) {
157       name = UNKNOWN_LOGGER_NAME;
158     }
159     return LoggerFactory.getLogger(name);
160   }
161 
162   protected void callLocationAwareLogger(LocationAwareLogger lal,
163       LogRecord record) {
164     int julLevelValue = record.getLevel().intValue();
165     int slf4jLevel;
166 
167     if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
168       slf4jLevel = LocationAwareLogger.TRACE_INT;
169     } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
170       slf4jLevel = LocationAwareLogger.DEBUG_INT;
171     } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
172       slf4jLevel = LocationAwareLogger.INFO_INT;
173     } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
174       slf4jLevel = LocationAwareLogger.WARN_INT;
175     } else {
176       slf4jLevel = LocationAwareLogger.ERROR_INT;
177     }
178     String i18nMessage = getMessageI18N(record);
179     lal.log(null, FQCN, slf4jLevel, i18nMessage, record.getThrown());
180   }
181 
182   protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
183     String i18nMessage = getMessageI18N(record);
184     int julLevelValue = record.getLevel().intValue();
185     if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
186       slf4jLogger.trace(i18nMessage, record.getThrown());
187     } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
188       slf4jLogger.debug(i18nMessage, record.getThrown());
189     } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
190       slf4jLogger.info(i18nMessage, record.getThrown());
191     } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
192       slf4jLogger.warn(i18nMessage, record.getThrown());
193     } else {
194       slf4jLogger.error(i18nMessage, record.getThrown());
195     }
196   }
197 
198   /**
199    * Get the record's message, possibly via a resource bundle.
200    * 
201    * @param record
202    * @return
203    */
204   private String getMessageI18N(LogRecord record) {
205     String message = record.getMessage();
206 
207     if (message == null) {
208       return null;
209     }
210 
211     ResourceBundle bundle = record.getResourceBundle();
212     if (bundle != null) {
213       try {
214         message = bundle.getString(message);
215       } catch (MissingResourceException e) {
216       }
217     }
218     Object[] params = record.getParameters();
219     if (params != null) {
220       message = MessageFormat.format(message, params);
221     }
222     return message;
223   }
224 
225   /**
226    * Publish a LogRecord.
227    * <p>
228    * The logging request was made initially to a Logger object, which
229    * initialized the LogRecord and forwarded it here.
230    * <p>
231    * This handler ignores the Level attached to the LogRecord, as SLF4J cares
232    * about discarding log statements.
233    * 
234    * @param record
235    *          Description of the log event. A null record is silently ignored
236    *          and is not published.
237    */
238   public void publish(LogRecord record) {
239     // Silently ignore null records.
240     if (record == null) {
241       return;
242     }
243 
244     Logger slf4jLogger = getSLF4JLogger(record);
245     String message = record.getMessage(); // can be null!
246     // this is a check to avoid calling the underlying logging system
247     // with a null message. While it is legitimate to invoke j.u.l. with
248     // a null message, other logging frameworks do not support this.
249     // see also http://bugzilla.slf4j.org/show_bug.cgi?id=108
250     if (message == null) {
251       message = "";
252     }
253     if (slf4jLogger instanceof LocationAwareLogger) {
254       callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
255     } else {
256       callPlainSLF4JLogger(slf4jLogger, record);
257     }
258   }
259 
260 }