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