1 /* 2 * Copyright (c) 2004-2008 QOS.ch 3 * 4 * All rights reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, and/or sell copies of the Software, and to permit persons 11 * to whom the Software is furnished to do so, provided that the above 12 * copyright notice(s) and this permission notice appear in all copies of 13 * the Software and that both the above copyright notice(s) and this 14 * permission notice appear in supporting documentation. 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 NONINFRINGEMENT 19 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY 21 * SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER 22 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 23 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 24 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25 * 26 * Except as contained in this notice, the name of a copyright holder 27 * shall not be used in advertising or otherwise to promote the sale, use 28 * or other dealings in this Software without prior written authorization 29 * of the copyright holder. 30 */ 31 32 package org.slf4j.bridge; 33 34 import java.text.MessageFormat; 35 import java.util.MissingResourceException; 36 import java.util.ResourceBundle; 37 import java.util.logging.Handler; 38 import java.util.logging.Level; 39 import java.util.logging.LogManager; 40 import java.util.logging.LogRecord; 41 42 import org.slf4j.Logger; 43 import org.slf4j.LoggerFactory; 44 import org.slf4j.spi.LocationAwareLogger; 45 46 // Based on http://bugzilla.slf4j.org/show_bug.cgi?id=38 47 48 /** 49 * Bridge/route all JUL log records to the SLF4J API. 50 * 51 * <p> 52 * Essentially, the idea is to install on the root logger an instance of 53 * SLF4JBridgeHandler as the sole JUL handler in the system. Subsequently, the 54 * SLF4JBridgeHandler instance will redirect all JUL log records are redirected 55 * to the SLF4J API based on the following mapping of levels: 56 * 57 * <pre> 58 * FINEST -> TRACE 59 * FINER -> DEBUG 60 * FINE -> DEBUG 61 * INFO -> INFO 62 * WARNING -> WARN 63 * SEVER -> ERROR 64 * </pre> 65 * 66 * Usage: 67 * 68 * <pre> 69 * // call only once during initialization time of your application 70 * SLF4JBridgeHandler.install(); 71 * 72 * // usual pattern: get a Logger and then log a message 73 * java.util.logging.Logger julLogger = java.util.logging.Logger 74 * .getLogger("org.wombat"); 75 * julLogger.fine("hello world"); // this will get redirected to SLF4J 76 * </pre> 77 * 78 * <p> 79 * Please note that translating a java.util.logging event into SLF4J incurs the 80 * cost of constructing {@link LogRecord} instance regardless of whether the 81 * SLF4J logger is disabled for the given level. <b>Consequently, j.u.l. to 82 * SLF4J translation can seriously impact on the cost of disabled logging 83 * statements (60 fold increase) and a measurable impact on enabled log 84 * statements (20% overall increase). </b> 85 * </p> 86 * 87 * <p> 88 * If application performance is a concern, then use of SLF4JBridgeHandler is 89 * appropriate only if few j.u.l. logging statements are in play. 90 * 91 * @author Christian Stein 92 * @author Joern Huxhorn 93 * @author Ceki Gülcü 94 * @author Darryl Smith 95 * 96 * @since 1.5.1 97 */ 98 public class SLF4JBridgeHandler extends Handler { 99 100 // The caller is java.util.logging.Logger 101 private static final String FQCN = java.util.logging.Logger.class.getName(); 102 private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger"; 103 104 private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue(); 105 private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue(); 106 private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue(); 107 private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue(); 108 109 /** 110 * Adds a SLF4JBridgeHandler instance to jul's root logger. 111 * 112 * <p> 113 * This handler will redirect jul logging to SLF4J. However, only logs enabled 114 * in j.u.l. will be redirected. For example, if a log statement invoking a 115 * j.u.l. logger disabled that statement, by definition, will <em>not</em> 116 * reach any SLF4JBridgeHandler instance and cannot be redirected. 117 */ 118 public static void install() { 119 LogManager.getLogManager().getLogger("").addHandler( 120 new SLF4JBridgeHandler()); 121 } 122 123 /** 124 * Removes previously installed SLF4JBridgeHandler instances. See also 125 * {@link #install()}. 126 * 127 * @throws SecurityException 128 * A <code>SecurityException</code> is thrown, if a security manager 129 * exists and if the caller does not have 130 * LoggingPermission("control"). 131 */ 132 public static void uninstall() throws SecurityException { 133 java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger( 134 ""); 135 Handler[] handlers = rootLogger.getHandlers(); 136 for (int i = 0; i < handlers.length; i++) { 137 if (handlers[i] instanceof SLF4JBridgeHandler) { 138 rootLogger.removeHandler(handlers[i]); 139 } 140 } 141 } 142 143 /** 144 * Initialize this handler. 145 * 146 */ 147 public SLF4JBridgeHandler() { 148 } 149 150 /** 151 * No-op implementation. 152 */ 153 public void close() { 154 // empty 155 } 156 157 /** 158 * No-op implementation. 159 */ 160 public void flush() { 161 // empty 162 } 163 164 /** 165 * Return the Logger instance that will be used for logging. 166 */ 167 protected Logger getSLF4JLogger(LogRecord record) { 168 String name = record.getLoggerName(); 169 if (name == null) { 170 name = UNKNOWN_LOGGER_NAME; 171 } 172 return LoggerFactory.getLogger(name); 173 } 174 175 protected void callLocationAwareLogger(LocationAwareLogger lal, 176 LogRecord record) { 177 int julLevelValue = record.getLevel().intValue(); 178 int slf4jLevel; 179 180 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) { 181 slf4jLevel = LocationAwareLogger.TRACE_INT; 182 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) { 183 slf4jLevel = LocationAwareLogger.DEBUG_INT; 184 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) { 185 slf4jLevel = LocationAwareLogger.INFO_INT; 186 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) { 187 slf4jLevel = LocationAwareLogger.WARN_INT; 188 } else { 189 slf4jLevel = LocationAwareLogger.ERROR_INT; 190 } 191 String i18nMessage = getMessageI18N(record); 192 lal.log(null, FQCN, slf4jLevel, i18nMessage, null, record.getThrown()); 193 } 194 195 protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) { 196 String i18nMessage = getMessageI18N(record); 197 int julLevelValue = record.getLevel().intValue(); 198 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) { 199 slf4jLogger.trace(i18nMessage, record.getThrown()); 200 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) { 201 slf4jLogger.debug(i18nMessage, record.getThrown()); 202 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) { 203 slf4jLogger.info(i18nMessage, record.getThrown()); 204 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) { 205 slf4jLogger.warn(i18nMessage, record.getThrown()); 206 } else { 207 slf4jLogger.error(i18nMessage, record.getThrown()); 208 } 209 } 210 211 /** 212 * Get the record's message, possibly via a resource bundle. 213 * 214 * @param record 215 * @return 216 */ 217 private String getMessageI18N(LogRecord record) { 218 String message = record.getMessage(); 219 220 if (message == null) { 221 return null; 222 } 223 224 ResourceBundle bundle = record.getResourceBundle(); 225 if (bundle != null) { 226 try { 227 message = bundle.getString(message); 228 } catch (MissingResourceException e) { 229 } 230 } 231 Object[] params = record.getParameters(); 232 if (params != null) { 233 message = MessageFormat.format(message, params); 234 } 235 return message; 236 } 237 238 /** 239 * Publish a LogRecord. 240 * <p> 241 * The logging request was made initially to a Logger object, which 242 * initialized the LogRecord and forwarded it here. 243 * <p> 244 * This handler ignores the Level attached to the LogRecord, as SLF4J cares 245 * about discarding log statements. 246 * 247 * @param record 248 * Description of the log event. A null record is silently ignored 249 * and is not published. 250 */ 251 public void publish(LogRecord record) { 252 // Silently ignore null records. 253 if (record == null) { 254 return; 255 } 256 257 Logger slf4jLogger = getSLF4JLogger(record); 258 String message = record.getMessage(); // can be null! 259 // this is a check to avoid calling the underlying logging system 260 // with a null message. While it is legitimate to invoke j.u.l. with 261 // a null message, other logging frameworks do not support this. 262 // see also http://bugzilla.slf4j.org/show_bug.cgi?id=108 263 if (message == null) { 264 message = ""; 265 } 266 if (slf4jLogger instanceof LocationAwareLogger) { 267 callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record); 268 } else { 269 callPlainSLF4JLogger(slf4jLogger, record); 270 } 271 } 272 273 }