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 }