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