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; 26 27 import java.io.IOException; 28 import java.net.URL; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Enumeration; 32 import java.util.LinkedHashSet; 33 import java.util.List; 34 import java.util.Set; 35 import java.util.concurrent.LinkedBlockingQueue; 36 37 import org.slf4j.event.SubstituteLoggingEvent; 38 import org.slf4j.helpers.NOPLoggerFactory; 39 import org.slf4j.helpers.SubstituteLogger; 40 import org.slf4j.helpers.SubstituteLoggerFactory; 41 import org.slf4j.helpers.Util; 42 import org.slf4j.impl.StaticLoggerBinder; 43 44 /** 45 * The <code>LoggerFactory</code> is a utility class producing Loggers for 46 * various logging APIs, most notably for log4j, logback and JDK 1.4 logging. 47 * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and 48 * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported. 49 * <p/> 50 * <p/> 51 * <code>LoggerFactory</code> is essentially a wrapper around an 52 * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at 53 * compile time. 54 * <p/> 55 * <p/> 56 * Please note that all methods in <code>LoggerFactory</code> are static. 57 * 58 * 59 * @author Alexander Dorokhine 60 * @author Robert Elliot 61 * @author Ceki Gülcü 62 * 63 */ 64 public final class LoggerFactory { 65 66 static final String CODES_PREFIX = "http://www.slf4j.org/codes.html"; 67 68 static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder"; 69 static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings"; 70 static final String NULL_LF_URL = CODES_PREFIX + "#null_LF"; 71 static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch"; 72 static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger"; 73 static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch"; 74 static final String REPLAY_URL = CODES_PREFIX + "#replay"; 75 76 static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit"; 77 static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory could not be successfully initialized. See also " + UNSUCCESSFUL_INIT_URL; 78 79 static final int UNINITIALIZED = 0; 80 static final int ONGOING_INITIALIZATION = 1; 81 static final int FAILED_INITIALIZATION = 2; 82 static final int SUCCESSFUL_INITIALIZATION = 3; 83 static final int NOP_FALLBACK_INITIALIZATION = 4; 84 85 static volatile int INITIALIZATION_STATE = UNINITIALIZED; 86 static SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory(); 87 static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory(); 88 89 // Support for detecting mismatched logger names. 90 static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch"; 91 static final String JAVA_VENDOR_PROPERTY = "java.vendor.url"; 92 93 static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY); 94 95 /** 96 * It is LoggerFactory's responsibility to track version changes and manage 97 * the compatibility list. 98 * <p/> 99 * <p/> 100 * It is assumed that all versions in the 1.6 are mutually compatible. 101 */ 102 static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" }; 103 104 // private constructor prevents instantiation LoggerFactory()105 private LoggerFactory() { 106 } 107 108 /** 109 * Force LoggerFactory to consider itself uninitialized. 110 * <p/> 111 * <p/> 112 * This method is intended to be called by classes (in the same package) for 113 * testing purposes. This method is internal. It can be modified, renamed or 114 * removed at any time without notice. 115 * <p/> 116 * <p/> 117 * You are strongly discouraged from calling this method in production code. 118 */ reset()119 static void reset() { 120 INITIALIZATION_STATE = UNINITIALIZED; 121 } 122 performInitialization()123 private final static void performInitialization() { 124 bind(); 125 if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 126 versionSanityCheck(); 127 } 128 } 129 messageContainsOrgSlf4jImplStaticLoggerBinder(String msg)130 private static boolean messageContainsOrgSlf4jImplStaticLoggerBinder(String msg) { 131 if (msg == null) 132 return false; 133 if (msg.contains("org/slf4j/impl/StaticLoggerBinder")) 134 return true; 135 if (msg.contains("org.slf4j.impl.StaticLoggerBinder")) 136 return true; 137 return false; 138 } 139 bind()140 private final static void bind() { 141 try { 142 Set<URL> staticLoggerBinderPathSet = null; 143 // skip check under android, see also 144 // http://jira.qos.ch/browse/SLF4J-328 145 if (!isAndroid()) { 146 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 147 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); 148 } 149 // the next line does the binding 150 StaticLoggerBinder.getSingleton(); 151 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 152 reportActualBinding(staticLoggerBinderPathSet); 153 fixSubstituteLoggers(); 154 replayEvents(); 155 // release all resources in SUBST_FACTORY 156 SUBST_FACTORY.clear(); 157 } catch (NoClassDefFoundError ncde) { 158 String msg = ncde.getMessage(); 159 if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { 160 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; 161 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); 162 Util.report("Defaulting to no-operation (NOP) logger implementation"); 163 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); 164 } else { 165 failedBinding(ncde); 166 throw ncde; 167 } 168 } catch (java.lang.NoSuchMethodError nsme) { 169 String msg = nsme.getMessage(); 170 if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) { 171 INITIALIZATION_STATE = FAILED_INITIALIZATION; 172 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); 173 Util.report("Your binding is version 1.5.5 or earlier."); 174 Util.report("Upgrade your binding to version 1.6.x."); 175 } 176 throw nsme; 177 } catch (Exception e) { 178 failedBinding(e); 179 throw new IllegalStateException("Unexpected initialization failure", e); 180 } 181 } 182 fixSubstituteLoggers()183 private static void fixSubstituteLoggers() { 184 synchronized (SUBST_FACTORY) { 185 SUBST_FACTORY.postInitialization(); 186 for (SubstituteLogger substLogger : SUBST_FACTORY.getLoggers()) { 187 Logger logger = getLogger(substLogger.getName()); 188 substLogger.setDelegate(logger); 189 } 190 } 191 192 } 193 failedBinding(Throwable t)194 static void failedBinding(Throwable t) { 195 INITIALIZATION_STATE = FAILED_INITIALIZATION; 196 Util.report("Failed to instantiate SLF4J LoggerFactory", t); 197 } 198 replayEvents()199 private static void replayEvents() { 200 final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_FACTORY.getEventQueue(); 201 final int queueSize = queue.size(); 202 int count = 0; 203 final int maxDrain = 128; 204 List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain); 205 while (true) { 206 int numDrained = queue.drainTo(eventList, maxDrain); 207 if (numDrained == 0) 208 break; 209 for (SubstituteLoggingEvent event : eventList) { 210 replaySingleEvent(event); 211 if (count++ == 0) 212 emitReplayOrSubstituionWarning(event, queueSize); 213 } 214 eventList.clear(); 215 } 216 } 217 emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize)218 private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) { 219 if (event.getLogger().isDelegateEventAware()) { 220 emitReplayWarning(queueSize); 221 } else if (event.getLogger().isDelegateNOP()) { 222 // nothing to do 223 } else { 224 emitSubstitutionWarning(); 225 } 226 } 227 replaySingleEvent(SubstituteLoggingEvent event)228 private static void replaySingleEvent(SubstituteLoggingEvent event) { 229 if (event == null) 230 return; 231 232 SubstituteLogger substLogger = event.getLogger(); 233 String loggerName = substLogger.getName(); 234 if (substLogger.isDelegateNull()) { 235 throw new IllegalStateException("Delegate logger cannot be null at this state."); 236 } 237 238 if (substLogger.isDelegateNOP()) { 239 // nothing to do 240 } else if (substLogger.isDelegateEventAware()) { 241 substLogger.log(event); 242 } else { 243 Util.report(loggerName); 244 } 245 } 246 emitSubstitutionWarning()247 private static void emitSubstitutionWarning() { 248 Util.report("The following set of substitute loggers may have been accessed"); 249 Util.report("during the initialization phase. Logging calls during this"); 250 Util.report("phase were not honored. However, subsequent logging calls to these"); 251 Util.report("loggers will work as normally expected."); 252 Util.report("See also " + SUBSTITUTE_LOGGER_URL); 253 } 254 emitReplayWarning(int eventCount)255 private static void emitReplayWarning(int eventCount) { 256 Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are"); 257 Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system."); 258 Util.report("See also " + REPLAY_URL); 259 } 260 versionSanityCheck()261 private final static void versionSanityCheck() { 262 try { 263 String requested = StaticLoggerBinder.REQUESTED_API_VERSION; 264 265 boolean match = false; 266 for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) { 267 if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) { 268 match = true; 269 } 270 } 271 if (!match) { 272 Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " 273 + Arrays.asList(API_COMPATIBILITY_LIST).toString()); 274 Util.report("See " + VERSION_MISMATCH + " for further details."); 275 } 276 } catch (java.lang.NoSuchFieldError nsfe) { 277 // given our large user base and SLF4J's commitment to backward 278 // compatibility, we cannot cry here. Only for implementations 279 // which willingly declare a REQUESTED_API_VERSION field do we 280 // emit compatibility warnings. 281 } catch (Throwable e) { 282 // we should never reach here 283 Util.report("Unexpected problem occured during version sanity check", e); 284 } 285 } 286 287 // We need to use the name of the StaticLoggerBinder class, but we can't 288 // reference 289 // the class itself. 290 private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; 291 findPossibleStaticLoggerBinderPathSet()292 static Set<URL> findPossibleStaticLoggerBinderPathSet() { 293 // use Set instead of list in order to deal with bug #138 294 // LinkedHashSet appropriate here because it preserves insertion order 295 // during iteration 296 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); 297 try { 298 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); 299 Enumeration<URL> paths; 300 if (loggerFactoryClassLoader == null) { 301 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 302 } else { 303 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 304 } 305 while (paths.hasMoreElements()) { 306 URL path = paths.nextElement(); 307 staticLoggerBinderPathSet.add(path); 308 } 309 } catch (IOException ioe) { 310 Util.report("Error getting resources from path", ioe); 311 } 312 return staticLoggerBinderPathSet; 313 } 314 isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet)315 private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet) { 316 return binderPathSet.size() > 1; 317 } 318 319 /** 320 * Prints a warning message on the console if multiple bindings were found 321 * on the class path. No reporting is done otherwise. 322 * 323 */ reportMultipleBindingAmbiguity(Set<URL> binderPathSet)324 private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) { 325 if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { 326 Util.report("Class path contains multiple SLF4J bindings."); 327 for (URL path : binderPathSet) { 328 Util.report("Found binding in [" + path + "]"); 329 } 330 Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); 331 } 332 } 333 isAndroid()334 private static boolean isAndroid() { 335 String vendor = Util.safeGetSystemProperty(JAVA_VENDOR_PROPERTY); 336 if (vendor == null) 337 return false; 338 return vendor.toLowerCase().contains("android"); 339 } 340 reportActualBinding(Set<URL> binderPathSet)341 private static void reportActualBinding(Set<URL> binderPathSet) { 342 // binderPathSet can be null under Android 343 if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { 344 Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]"); 345 } 346 } 347 348 /** 349 * Return a logger named according to the name parameter using the 350 * statically bound {@link ILoggerFactory} instance. 351 * 352 * @param name 353 * The name of the logger. 354 * @return logger 355 */ getLogger(String name)356 public static Logger getLogger(String name) { 357 ILoggerFactory iLoggerFactory = getILoggerFactory(); 358 return iLoggerFactory.getLogger(name); 359 } 360 361 /** 362 * Return a logger named corresponding to the class passed as parameter, 363 * using the statically bound {@link ILoggerFactory} instance. 364 * 365 * <p> 366 * In case the the <code>clazz</code> parameter differs from the name of the 367 * caller as computed internally by SLF4J, a logger name mismatch warning 368 * will be printed but only if the 369 * <code>slf4j.detectLoggerNameMismatch</code> system property is set to 370 * true. By default, this property is not set and no warnings will be 371 * printed even in case of a logger name mismatch. 372 * 373 * @param clazz 374 * the returned logger will be named after clazz 375 * @return logger 376 * 377 * 378 * @see <a 379 * href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected 380 * logger name mismatch</a> 381 */ getLogger(Class<?> clazz)382 public static Logger getLogger(Class<?> clazz) { 383 Logger logger = getLogger(clazz.getName()); 384 if (DETECT_LOGGER_NAME_MISMATCH) { 385 Class<?> autoComputedCallingClass = Util.getCallingClass(); 386 if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 387 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 388 autoComputedCallingClass.getName())); 389 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 390 } 391 } 392 return logger; 393 } 394 nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass)395 private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) { 396 return !autoComputedCallingClass.isAssignableFrom(clazz); 397 } 398 399 /** 400 * Return the {@link ILoggerFactory} instance in use. 401 * <p/> 402 * <p/> 403 * ILoggerFactory instance is bound with this class at compile time. 404 * 405 * @return the ILoggerFactory instance in use 406 */ getILoggerFactory()407 public static ILoggerFactory getILoggerFactory() { 408 if (INITIALIZATION_STATE == UNINITIALIZED) { 409 synchronized (LoggerFactory.class) { 410 if (INITIALIZATION_STATE == UNINITIALIZED) { 411 INITIALIZATION_STATE = ONGOING_INITIALIZATION; 412 performInitialization(); 413 } 414 } 415 } 416 switch (INITIALIZATION_STATE) { 417 case SUCCESSFUL_INITIALIZATION: 418 return StaticLoggerBinder.getSingleton().getLoggerFactory(); 419 case NOP_FALLBACK_INITIALIZATION: 420 return NOP_FALLBACK_FACTORY; 421 case FAILED_INITIALIZATION: 422 throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 423 case ONGOING_INITIALIZATION: 424 // support re-entrant behavior. 425 // See also http://jira.qos.ch/browse/SLF4J-97 426 return SUBST_FACTORY; 427 } 428 throw new IllegalStateException("Unreachable code"); 429 } 430 } 431