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://bugzilla.slf4j.org/show_bug.cgi?id=38
40
41 /**
42 * Bridge/route all JUL log records to the SLF4J API.
43 *
44 * <p>
45 * Essentially, the idea is to install on the root logger an instance of
46 * SLF4JBridgeHandler as the sole JUL handler in the system. Subsequently, the
47 * SLF4JBridgeHandler instance will redirect all JUL log records are redirected
48 * to the SLF4J API based on the following mapping of levels:
49 *
50 * <pre>
51 * FINEST -> TRACE
52 * FINER -> DEBUG
53 * FINE -> DEBUG
54 * INFO -> INFO
55 * WARNING -> WARN
56 * SEVER -> ERROR
57 * </pre>
58 *
59 * Usage:
60 *
61 * <pre>
62 * // call only once during initialization time of your application
63 * SLF4JBridgeHandler.install();
64 *
65 * // usual pattern: get a Logger and then log a message
66 * java.util.logging.Logger julLogger = java.util.logging.Logger
67 * .getLogger("org.wombat");
68 * julLogger.fine("hello world"); // this will get redirected to SLF4J
69 * </pre>
70 *
71 * <p>
72 * Please note that translating a java.util.logging event into SLF4J incurs the
73 * cost of constructing {@link LogRecord} instance regardless of whether the
74 * SLF4J logger is disabled for the given level. <b>Consequently, j.u.l. to
75 * SLF4J translation can seriously impact on the cost of disabled logging
76 * statements (60 fold increase) and a measurable impact on enabled log
77 * statements (20% overall increase). </b>
78 * </p>
79 *
80 * <p>
81 * If application performance is a concern, then use of SLF4JBridgeHandler is
82 * appropriate only if few j.u.l. logging statements are in play.
83 *
84 * @author Christian Stein
85 * @author Joern Huxhorn
86 * @author Ceki Gülcü
87 * @author Darryl Smith
88 *
89 * @since 1.5.1
90 */
91 public class SLF4JBridgeHandler extends Handler {
92
93 // The caller is java.util.logging.Logger
94 private static final String FQCN = java.util.logging.Logger.class.getName();
95 private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
96
97 private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
98 private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
99 private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
100 private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
101
102 /**
103 * Adds a SLF4JBridgeHandler instance to jul's root logger.
104 *
105 * <p>
106 * This handler will redirect jul logging to SLF4J. However, only logs enabled
107 * in j.u.l. will be redirected. For example, if a log statement invoking a
108 * j.u.l. logger disabled that statement, by definition, will <em>not</em>
109 * reach any SLF4JBridgeHandler instance and cannot be redirected.
110 */
111 public static void install() {
112 LogManager.getLogManager().getLogger("").addHandler(
113 new SLF4JBridgeHandler());
114 }
115
116 /**
117 * Removes previously installed SLF4JBridgeHandler instances. See also
118 * {@link #install()}.
119 *
120 * @throws SecurityException
121 * A <code>SecurityException</code> is thrown, if a security manager
122 * exists and if the caller does not have
123 * LoggingPermission("control").
124 */
125 public static void uninstall() throws SecurityException {
126 java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(
127 "");
128 Handler[] handlers = rootLogger.getHandlers();
129 for (int i = 0; i < handlers.length; i++) {
130 if (handlers[i] instanceof SLF4JBridgeHandler) {
131 rootLogger.removeHandler(handlers[i]);
132 }
133 }
134 }
135
136 /**
137 * Returns true if SLF4JBridgeHandler has been previously installed, returns false otherwise.
138 * @return
139 * @throws SecurityException
140 */
141 public static boolean isInstalled() throws SecurityException {
142 java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(
143 "");
144 Handler[] handlers = rootLogger.getHandlers();
145 for (int i = 0; i < handlers.length; i++) {
146 if (handlers[i] instanceof SLF4JBridgeHandler) {
147 return true;
148 }
149 }
150 return false;
151 }
152
153
154 /**
155 * Initialize this handler.
156 *
157 */
158 public SLF4JBridgeHandler() {
159 }
160
161 /**
162 * No-op implementation.
163 */
164 public void close() {
165 // empty
166 }
167
168 /**
169 * No-op implementation.
170 */
171 public void flush() {
172 // empty
173 }
174
175 /**
176 * Return the Logger instance that will be used for logging.
177 */
178 protected Logger getSLF4JLogger(LogRecord record) {
179 String name = record.getLoggerName();
180 if (name == null) {
181 name = UNKNOWN_LOGGER_NAME;
182 }
183 return LoggerFactory.getLogger(name);
184 }
185
186 protected void callLocationAwareLogger(LocationAwareLogger lal,
187 LogRecord record) {
188 int julLevelValue = record.getLevel().intValue();
189 int slf4jLevel;
190
191 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
192 slf4jLevel = LocationAwareLogger.TRACE_INT;
193 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
194 slf4jLevel = LocationAwareLogger.DEBUG_INT;
195 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
196 slf4jLevel = LocationAwareLogger.INFO_INT;
197 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
198 slf4jLevel = LocationAwareLogger.WARN_INT;
199 } else {
200 slf4jLevel = LocationAwareLogger.ERROR_INT;
201 }
202 String i18nMessage = getMessageI18N(record);
203 lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown());
204 }
205
206 protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
207 String i18nMessage = getMessageI18N(record);
208 int julLevelValue = record.getLevel().intValue();
209 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
210 slf4jLogger.trace(i18nMessage, record.getThrown());
211 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
212 slf4jLogger.debug(i18nMessage, record.getThrown());
213 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
214 slf4jLogger.info(i18nMessage, record.getThrown());
215 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
216 slf4jLogger.warn(i18nMessage, record.getThrown());
217 } else {
218 slf4jLogger.error(i18nMessage, record.getThrown());
219 }
220 }
221
222 /**
223 * Get the record's message, possibly via a resource bundle.
224 *
225 * @param record
226 * @return
227 */
228 private String getMessageI18N(LogRecord record) {
229 String message = record.getMessage();
230
231 if (message == null) {
232 return null;
233 }
234
235 ResourceBundle bundle = record.getResourceBundle();
236 if (bundle != null) {
237 try {
238 message = bundle.getString(message);
239 } catch (MissingResourceException e) {
240 }
241 }
242 Object[] params = record.getParameters();
243 if (params != null) {
244 message = MessageFormat.format(message, params);
245 }
246 return message;
247 }
248
249 /**
250 * Publish a LogRecord.
251 * <p>
252 * The logging request was made initially to a Logger object, which
253 * initialized the LogRecord and forwarded it here.
254 * <p>
255 * This handler ignores the Level attached to the LogRecord, as SLF4J cares
256 * about discarding log statements.
257 *
258 * @param record
259 * Description of the log event. A null record is silently ignored
260 * and is not published.
261 */
262 public void publish(LogRecord record) {
263 // Silently ignore null records.
264 if (record == null) {
265 return;
266 }
267
268 Logger slf4jLogger = getSLF4JLogger(record);
269 String message = record.getMessage(); // can be null!
270 // this is a check to avoid calling the underlying logging system
271 // with a null message. While it is legitimate to invoke j.u.l. with
272 // a null message, other logging frameworks do not support this.
273 // see also http://bugzilla.slf4j.org/show_bug.cgi?id=108
274 if (message == null) {
275 message = "";
276 }
277 if (slf4jLogger instanceof LocationAwareLogger) {
278 callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
279 } else {
280 callPlainSLF4JLogger(slf4jLogger, record);
281 }
282 }
283
284 }