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 -> TRACE
50 * FINER -> DEBUG
51 * FINE -> DEBUG
52 * INFO -> INFO
53 * WARNING -> WARN
54 * SEVERE -> 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("org.wombat");
74 * julLogger.fine("hello world"); // 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ülcü
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 }