001/** 002 * Copyright (c) 2004-2011 QOS.ch 003 * All rights reserved. 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining 006 * a copy of this software and associated documentation files (the 007 * "Software"), to deal in the Software without restriction, including 008 * without limitation the rights to use, copy, modify, merge, publish, 009 * distribute, sublicense, and/or sell copies of the Software, and to 010 * permit persons to whom the Software is furnished to do so, subject to 011 * the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be 014 * included in all copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 017 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 018 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 021 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 023 * 024 */ 025package org.slf4j.bridge; 026 027import java.text.MessageFormat; 028import java.util.MissingResourceException; 029import java.util.ResourceBundle; 030import java.util.logging.Handler; 031import java.util.logging.Level; 032import java.util.logging.LogManager; 033import java.util.logging.LogRecord; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037import org.slf4j.spi.LocationAwareLogger; 038 039// Based on http://jira.qos.ch/browse/SLF4J-30 040 041/** 042 * <p>Bridge/route all JUL log records to the SLF4J API. 043 * <p>Essentially, the idea is to install on the root logger an instance of 044 * {@link SLF4JBridgeHandler} as the sole JUL handler in the system. Subsequently, the 045 * <code>SLF4JBridgeHandler</code> instance will redirect all JUL log records are redirected 046 * to the SLF4J API based on the following mapping of levels: 047 * 048 * <pre> 049 * FINEST -> TRACE 050 * FINER -> DEBUG 051 * FINE -> DEBUG 052 * INFO -> INFO 053 * WARNING -> WARN 054 * SEVERE -> ERROR</pre> 055 * <p><b>Programmatic installation:</b> 056 * <pre> 057 * // Optionally remove existing handlers attached to j.u.l root logger 058 * SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5) 059 060 * // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during 061 * // the initialization phase of your application 062 * SLF4JBridgeHandler.install();</pre> 063 * <p><b>Installation via <em>logging.properties</em> configuration file:</b> 064 * <pre> 065 * // register SLF4JBridgeHandler as handler for the j.u.l. root logger 066 * handlers = org.slf4j.bridge.SLF4JBridgeHandler</pre> 067 * <p>Once SLF4JBridgeHandler is installed, logging by j.u.l. loggers will be directed to 068 * SLF4J. Example: 069 * <pre> 070 * import java.util.logging.Logger; 071 * ... 072 * // usual pattern: get a Logger and then log a message 073 * Logger julLogger = Logger.getLogger("org.wombat"); 074 * julLogger.fine("hello world"); // this will get redirected to SLF4J</pre> 075 * 076 * <p>Please note that translating a java.util.logging event into SLF4J incurs the 077 * cost of constructing {@link LogRecord} instance regardless of whether the 078 * SLF4J logger is disabled for the given level. <b>Consequently, j.u.l. to 079 * SLF4J translation can seriously increase the cost of disabled logging 080 * statements (60 fold or 6000% increase) and measurably impact the performance of enabled log 081 * statements (20% overall increase).</b> Please note that as of logback-version 0.9.25, 082 * it is possible to completely eliminate the 60-fold translation overhead for disabled 083 * log statements with the help of <a href="http://logback.qos.ch/manual/configuration.html#LevelChangePropagator">LevelChangePropagator</a>. 084 * 085 * 086 * <p>If you are concerned about application performance, then use of <code>SLF4JBridgeHandler</code> 087 * is appropriate only if any of the following conditions is true: 088 * <ol> 089 * <li>few j.u.l. logging statements are in play</li> 090 * <li>LevelChangePropagator has been installed</li> 091 * </ol> 092 * 093 * <h2>As a Java 9/Jigsaw module</h2> 094 * 095 * <p>As of SLF4J 2.0x, the module name for <em>jul-to-slf4j.jar</em> is 096 * <b>jul.to.slf4j</b>. In SLF4J 1.7.x it was declared via an Automatic-Module-Name statement 097 * as <b>jul_to_slf4j</b> (note the two underscores). 098 * 099 * @author Christian Stein 100 * @author Joern Huxhorn 101 * @author Ceki Gülcü 102 * @author Darryl Smith 103 * @since 1.5.1 104 */ 105public class SLF4JBridgeHandler extends Handler { 106 107 // The caller is java.util.logging.Logger 108 private static final String FQCN = java.util.logging.Logger.class.getName(); 109 private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger"; 110 111 private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue(); 112 private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue(); 113 private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue(); 114 private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue(); 115 116 /** 117 * Adds a SLF4JBridgeHandler instance to jul's root logger. 118 * 119 * <p>This handler will redirect j.u.l. logging to SLF4J. However, only logs enabled 120 * in j.u.l. will be redirected. For example, if a log statement invoking a 121 * j.u.l. logger is disabled, then the corresponding non-event will <em>not</em> 122 * reach SLF4JBridgeHandler and cannot be redirected. 123 */ 124 public static void install() { 125 LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler()); 126 } 127 128 private static java.util.logging.Logger getRootLogger() { 129 return LogManager.getLogManager().getLogger(""); 130 } 131 132 /** 133 * Removes previously installed SLF4JBridgeHandler instances. See also 134 * {@link #install()}. 135 * 136 * @throws SecurityException A <code>SecurityException</code> is thrown, if a security manager 137 * exists and if the caller does not have 138 * LoggingPermission("control"). 139 */ 140 public static void uninstall() throws SecurityException { 141 java.util.logging.Logger rootLogger = getRootLogger(); 142 Handler[] handlers = rootLogger.getHandlers(); 143 for (Handler handler : handlers) { 144 if (handler instanceof SLF4JBridgeHandler) { 145 rootLogger.removeHandler(handler); 146 } 147 } 148 } 149 150 /** 151 * Returns true if SLF4JBridgeHandler has been previously installed, returns false otherwise. 152 * 153 * @return true if SLF4JBridgeHandler is already installed, false otherwise 154 * 155 */ 156 public static boolean isInstalled() { 157 java.util.logging.Logger rootLogger = getRootLogger(); 158 Handler[] handlers = rootLogger.getHandlers(); 159 for (Handler handler : handlers) { 160 if (handler instanceof SLF4JBridgeHandler) { 161 return true; 162 } 163 } 164 return false; 165 } 166 167 /** 168 * Invoking this method removes/unregisters/detaches all handlers currently attached to the root logger 169 * @since 1.6.5 170 */ 171 public static void removeHandlersForRootLogger() { 172 java.util.logging.Logger rootLogger = getRootLogger(); 173 java.util.logging.Handler[] handlers = rootLogger.getHandlers(); 174 for (Handler handler : handlers) { 175 rootLogger.removeHandler(handler); 176 } 177 } 178 179 /** 180 * Initialize this handler. 181 */ 182 public SLF4JBridgeHandler() { 183 } 184 185 /** 186 * No-op implementation. 187 */ 188 public void close() { 189 // empty 190 } 191 192 /** 193 * No-op implementation. 194 */ 195 public void flush() { 196 // empty 197 } 198 199 /** 200 * Return the Logger instance that will be used for logging. 201 * 202 * @param record a LogRecord 203 * @return an SLF4J logger corresponding to the record parameter's logger name 204 */ 205 protected Logger getSLF4JLogger(LogRecord record) { 206 String name = record.getLoggerName(); 207 if (name == null) { 208 name = UNKNOWN_LOGGER_NAME; 209 } 210 return LoggerFactory.getLogger(name); 211 } 212 213 protected void callLocationAwareLogger(LocationAwareLogger lal, LogRecord record) { 214 int julLevelValue = record.getLevel().intValue(); 215 int slf4jLevel; 216 217 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) { 218 slf4jLevel = LocationAwareLogger.TRACE_INT; 219 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) { 220 slf4jLevel = LocationAwareLogger.DEBUG_INT; 221 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) { 222 slf4jLevel = LocationAwareLogger.INFO_INT; 223 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) { 224 slf4jLevel = LocationAwareLogger.WARN_INT; 225 } else { 226 slf4jLevel = LocationAwareLogger.ERROR_INT; 227 } 228 String i18nMessage = getMessageI18N(record); 229 lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown()); 230 } 231 232 protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) { 233 String i18nMessage = getMessageI18N(record); 234 int julLevelValue = record.getLevel().intValue(); 235 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) { 236 slf4jLogger.trace(i18nMessage, record.getThrown()); 237 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) { 238 slf4jLogger.debug(i18nMessage, record.getThrown()); 239 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) { 240 slf4jLogger.info(i18nMessage, record.getThrown()); 241 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) { 242 slf4jLogger.warn(i18nMessage, record.getThrown()); 243 } else { 244 slf4jLogger.error(i18nMessage, record.getThrown()); 245 } 246 } 247 248 /** 249 * Get the record's message, possibly via a resource bundle. 250 * 251 * @param record 252 * @return 253 */ 254 private String getMessageI18N(LogRecord record) { 255 String message = record.getMessage(); 256 257 if (message == null) { 258 return null; 259 } 260 261 ResourceBundle bundle = record.getResourceBundle(); 262 if (bundle != null) { 263 try { 264 message = bundle.getString(message); 265 } catch (MissingResourceException e) { 266 } 267 } 268 Object[] params = record.getParameters(); 269 // avoid formatting when there are no or 0 parameters. see also 270 // http://jira.qos.ch/browse/SLF4J-203 271 if (params != null && params.length > 0) { 272 try { 273 message = MessageFormat.format(message, params); 274 } catch (IllegalArgumentException e) { 275 // default to the same behavior as in java.util.logging.Formatter.formatMessage(LogRecord) 276 // see also http://jira.qos.ch/browse/SLF4J-337 277 return message; 278 } 279 } 280 return message; 281 } 282 283 /** 284 * Publish a LogRecord. 285 * <p> 286 * The logging request was made initially to a Logger object, which 287 * initialized the LogRecord and forwarded it here. 288 * <p> 289 * This handler ignores the Level attached to the LogRecord, as SLF4J cares 290 * about discarding log statements. 291 * 292 * @param record Description of the log event. A null record is silently ignored 293 * and is not published. 294 */ 295 public void publish(LogRecord record) { 296 // Silently ignore null records. 297 if (record == null) { 298 return; 299 } 300 301 Logger slf4jLogger = getSLF4JLogger(record); 302 // this is a check to avoid calling the underlying logging system 303 // with a null message. While it is legitimate to invoke j.u.l. with 304 // a null message, other logging frameworks do not support this. 305 // see also http://jira.qos.ch/browse/SLF4J-99 306 if (record.getMessage() == null) { 307 record.setMessage(""); 308 } 309 if (slf4jLogger instanceof LocationAwareLogger) { 310 callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record); 311 } else { 312 callPlainSLF4JLogger(slf4jLogger, record); 313 } 314 } 315 316}