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 -> TRACE
59 * FINER -> DEBUG
60 * FINE -> DEBUG
61 * INFO -> INFO
62 * WARNING -> WARN
63 * SEVER -> 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("org.wombat");
75 * julLogger.fine("hello world"); // 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ülcü
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, 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 }