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 * 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 }