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; 026 027import java.io.IOException; 028import java.lang.reflect.Constructor; 029import java.lang.reflect.InvocationTargetException; 030import java.net.URL; 031import java.security.AccessController; 032import java.security.PrivilegedAction; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.Enumeration; 036import java.util.Iterator; 037import java.util.LinkedHashSet; 038import java.util.List; 039import java.util.ServiceConfigurationError; 040import java.util.ServiceLoader; 041import java.util.Set; 042import java.util.concurrent.LinkedBlockingQueue; 043 044import org.slf4j.event.SubstituteLoggingEvent; 045import org.slf4j.helpers.NOP_FallbackServiceProvider; 046import org.slf4j.helpers.Reporter; 047import org.slf4j.helpers.SubstituteLogger; 048import org.slf4j.helpers.SubstituteServiceProvider; 049import org.slf4j.helpers.Util; 050import org.slf4j.spi.MDCAdapter; 051import org.slf4j.spi.SLF4JServiceProvider; 052 053/** 054 * The <code>LoggerFactory</code> is a utility class producing Loggers for 055 * various logging APIs, e.g. logback, reload4j, log4j and JDK 1.4 logging. 056 * Other implementations such as {@link org.slf4j.helpers.NOPLogger NOPLogger} and 057 * SimpleLogger are also supported. 058 * 059 * <p><code>LoggerFactory</code> is essentially a wrapper around an 060 * {@link ILoggerFactory} instance provided by a {@link SLF4JServiceProvider}. 061 * 062 * <p> 063 * Please note that all methods in <code>LoggerFactory</code> are static. 064 * 065 * @author Alexander Dorokhine 066 * @author Robert Elliot 067 * @author Ceki Gülcü 068 * 069 */ 070public final class LoggerFactory { 071 072 static final String CODES_PREFIX = "https://www.slf4j.org/codes.html"; 073 074 static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders"; 075 static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings"; 076 077 static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings"; 078 static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch"; 079 static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger"; 080 static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch"; 081 static final String REPLAY_URL = CODES_PREFIX + "#replay"; 082 083 static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit"; 084 static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also " 085 + UNSUCCESSFUL_INIT_URL; 086 087 static final String CONNECTED_WITH_MSG = "Connected with provider of type ["; 088 089 /** 090 * System property for explicitly setting the provider class. If set and the provider could be instantiated, 091 * then the service loading mechanism will be bypassed. 092 * 093 * @since 2.0.9 094 */ 095 static final public String PROVIDER_PROPERTY_KEY = "slf4j.provider"; 096 097 static final int UNINITIALIZED = 0; 098 static final int ONGOING_INITIALIZATION = 1; 099 static final int FAILED_INITIALIZATION = 2; 100 static final int SUCCESSFUL_INITIALIZATION = 3; 101 static final int NOP_FALLBACK_INITIALIZATION = 4; 102 103 static volatile int INITIALIZATION_STATE = UNINITIALIZED; 104 static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider(); 105 static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider(); 106 107 // Support for detecting mismatched logger names. 108 static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch"; 109 static final String JAVA_VENDOR_PROPERTY = "java.vendor.url"; 110 111 static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY); 112 113 static volatile SLF4JServiceProvider PROVIDER; 114 115 // Package access for tests 116 static List<SLF4JServiceProvider> findServiceProviders() { 117 List<SLF4JServiceProvider> providerList = new ArrayList<>(); 118 119 // retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that 120 // loaded the present class to search for services 121 final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader(); 122 123 SLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory); 124 if(explicitProvider != null) { 125 providerList.add(explicitProvider); 126 return providerList; 127 } 128 129 130 ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory); 131 132 Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator(); 133 while (iterator.hasNext()) { 134 safelyInstantiate(providerList, iterator); 135 } 136 return providerList; 137 } 138 139 private static ServiceLoader<SLF4JServiceProvider> getServiceLoader(final ClassLoader classLoaderOfLoggerFactory) { 140 ServiceLoader<SLF4JServiceProvider> serviceLoader; 141 SecurityManager securityManager = System.getSecurityManager(); 142 if(securityManager == null) { 143 serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory); 144 } else { 145 final PrivilegedAction<ServiceLoader<SLF4JServiceProvider>> action = () -> ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory); 146 serviceLoader = AccessController.doPrivileged(action); 147 } 148 return serviceLoader; 149 } 150 151 private static void safelyInstantiate(List<SLF4JServiceProvider> providerList, Iterator<SLF4JServiceProvider> iterator) { 152 try { 153 SLF4JServiceProvider provider = iterator.next(); 154 providerList.add(provider); 155 } catch (ServiceConfigurationError e) { 156 Reporter.error("A service provider failed to instantiate:\n" + e.getMessage()); 157 } 158 } 159 160 /** 161 * It is LoggerFactory's responsibility to track version changes and manage 162 * the compatibility list. 163 * <p> 164 */ 165 static private final String[] API_COMPATIBILITY_LIST = new String[] { "2.0" }; 166 167 // private constructor prevents instantiation 168 private LoggerFactory() { 169 } 170 171 /** 172 * Force LoggerFactory to consider itself uninitialized. 173 * <p> 174 * <p> 175 * This method is intended to be called by classes (in the same package) for 176 * testing purposes. This method is internal. It can be modified, renamed or 177 * removed at any time without notice. 178 * <p> 179 * <p> 180 * You are strongly discouraged from calling this method in production code. 181 */ 182 static void reset() { 183 INITIALIZATION_STATE = UNINITIALIZED; 184 } 185 186 private final static void performInitialization() { 187 bind(); 188 if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 189 versionSanityCheck(); 190 } 191 } 192 193 private final static void bind() { 194 try { 195 List<SLF4JServiceProvider> providersList = findServiceProviders(); 196 reportMultipleBindingAmbiguity(providersList); 197 if (providersList != null && !providersList.isEmpty()) { 198 PROVIDER = providersList.get(0); 199 earlyBindMDCAdapter(); 200 // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else. 201 PROVIDER.initialize(); 202 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 203 reportActualBinding(providersList); 204 } else { 205 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; 206 Reporter.warn("No SLF4J providers were found."); 207 Reporter.warn("Defaulting to no-operation (NOP) logger implementation"); 208 Reporter.warn("See " + NO_PROVIDERS_URL + " for further details."); 209 210 Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 211 reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet); 212 } 213 postBindCleanUp(); 214 } catch (Exception e) { 215 failedBinding(e); 216 throw new IllegalStateException("Unexpected initialization failure", e); 217 } 218 } 219 220 /** 221 * The value of PROVIDER.getMDCAdapter() can be null while PROVIDER has not yet initialized. 222 * 223 * However, SLF4JServiceProvider implementations are expected to initialize their internal 224 * MDCAdapter field in their constructor or on field declaration. 225 */ 226 private static void earlyBindMDCAdapter() { 227 MDCAdapter mdcAdapter = PROVIDER.getMDCAdapter(); 228 if(mdcAdapter != null) { 229 MDC.setMDCAdapter(mdcAdapter); 230 } 231 } 232 233 static SLF4JServiceProvider loadExplicitlySpecified(ClassLoader classLoader) { 234 String explicitlySpecified = System.getProperty(PROVIDER_PROPERTY_KEY); 235 if (null == explicitlySpecified || explicitlySpecified.isEmpty()) { 236 return null; 237 } 238 try { 239 String message = String.format("Attempting to load provider \"%s\" specified via \"%s\" system property", explicitlySpecified, PROVIDER_PROPERTY_KEY); 240 Reporter.info(message); 241 Class<?> clazz = classLoader.loadClass(explicitlySpecified); 242 Constructor<?> constructor = clazz.getConstructor(); 243 Object provider = constructor.newInstance(); 244 return (SLF4JServiceProvider) provider; 245 } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { 246 String message = String.format("Failed to instantiate the specified SLF4JServiceProvider (%s)", explicitlySpecified); 247 Reporter.error(message, e); 248 return null; 249 } catch (ClassCastException e) { 250 String message = String.format("Specified SLF4JServiceProvider (%s) does not implement SLF4JServiceProvider interface", explicitlySpecified); 251 Reporter.error(message, e); 252 return null; 253 } 254 } 255 256 private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) { 257 if (staticLoggerBinderPathSet.isEmpty()) { 258 return; 259 } 260 Reporter.warn("Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier."); 261 262 for (URL path : staticLoggerBinderPathSet) { 263 Reporter.warn("Ignoring binding found at [" + path + "]"); 264 } 265 Reporter.warn("See " + IGNORED_BINDINGS_URL + " for an explanation."); 266 267 } 268 269 // We need to use the name of the StaticLoggerBinder class, but we can't 270 // reference the class itself. 271 private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; 272 273 static Set<URL> findPossibleStaticLoggerBinderPathSet() { 274 // use Set instead of list in order to deal with bug #138 275 // LinkedHashSet appropriate here because it preserves insertion order 276 // during iteration 277 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>(); 278 try { 279 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); 280 Enumeration<URL> paths; 281 if (loggerFactoryClassLoader == null) { 282 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 283 } else { 284 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 285 } 286 while (paths.hasMoreElements()) { 287 URL path = paths.nextElement(); 288 staticLoggerBinderPathSet.add(path); 289 } 290 } catch (IOException ioe) { 291 Reporter.error("Error getting resources from path", ioe); 292 } 293 return staticLoggerBinderPathSet; 294 } 295 296 private static void postBindCleanUp() { 297 fixSubstituteLoggers(); 298 replayEvents(); 299 // release all resources in SUBST_FACTORY 300 SUBST_PROVIDER.getSubstituteLoggerFactory().clear(); 301 } 302 303 private static void fixSubstituteLoggers() { 304 synchronized (SUBST_PROVIDER) { 305 SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization(); 306 for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) { 307 Logger logger = getLogger(substLogger.getName()); 308 substLogger.setDelegate(logger); 309 } 310 } 311 312 } 313 314 static void failedBinding(Throwable t) { 315 INITIALIZATION_STATE = FAILED_INITIALIZATION; 316 Reporter.error("Failed to instantiate SLF4J LoggerFactory", t); 317 } 318 319 private static void replayEvents() { 320 final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue(); 321 final int queueSize = queue.size(); 322 int count = 0; 323 final int maxDrain = 128; 324 List<SubstituteLoggingEvent> eventList = new ArrayList<>(maxDrain); 325 while (true) { 326 int numDrained = queue.drainTo(eventList, maxDrain); 327 if (numDrained == 0) 328 break; 329 for (SubstituteLoggingEvent event : eventList) { 330 replaySingleEvent(event); 331 if (count++ == 0) 332 emitReplayOrSubstituionWarning(event, queueSize); 333 } 334 eventList.clear(); 335 } 336 } 337 338 private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) { 339 if (event.getLogger().isDelegateEventAware()) { 340 emitReplayWarning(queueSize); 341 } else if (event.getLogger().isDelegateNOP()) { 342 // nothing to do 343 } else { 344 emitSubstitutionWarning(); 345 } 346 } 347 348 private static void replaySingleEvent(SubstituteLoggingEvent event) { 349 if (event == null) 350 return; 351 352 SubstituteLogger substLogger = event.getLogger(); 353 String loggerName = substLogger.getName(); 354 if (substLogger.isDelegateNull()) { 355 throw new IllegalStateException("Delegate logger cannot be null at this state."); 356 } 357 358 if (substLogger.isDelegateNOP()) { 359 // nothing to do 360 } else if (substLogger.isDelegateEventAware()) { 361 if(substLogger.isEnabledForLevel(event.getLevel())) { 362 substLogger.log(event); 363 } 364 } else { 365 Reporter.warn(loggerName); 366 } 367 } 368 369 private static void emitSubstitutionWarning() { 370 Reporter.warn("The following set of substitute loggers may have been accessed"); 371 Reporter.warn("during the initialization phase. Logging calls during this"); 372 Reporter.warn("phase were not honored. However, subsequent logging calls to these"); 373 Reporter.warn("loggers will work as normally expected."); 374 Reporter.warn("See also " + SUBSTITUTE_LOGGER_URL); 375 } 376 377 private static void emitReplayWarning(int eventCount) { 378 Reporter.warn("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are"); 379 Reporter.warn("now being replayed. These are subject to the filtering rules of the underlying logging system."); 380 Reporter.warn("See also " + REPLAY_URL); 381 } 382 383 private final static void versionSanityCheck() { 384 try { 385 String requested = PROVIDER.getRequestedApiVersion(); 386 387 boolean match = false; 388 for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) { 389 if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) { 390 match = true; 391 } 392 } 393 if (!match) { 394 Reporter.warn("The requested version " + requested + " by your slf4j provider is not compatible with " 395 + Arrays.asList(API_COMPATIBILITY_LIST).toString()); 396 Reporter.warn("See " + VERSION_MISMATCH + " for further details."); 397 } 398 } catch (Throwable e) { 399 // we should never reach here 400 Reporter.error("Unexpected problem occurred during version sanity check", e); 401 } 402 } 403 404 private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) { 405 return providerList.size() > 1; 406 } 407 408 /** 409 * Prints a warning message on the console if multiple bindings were found 410 * on the class path. No reporting is done otherwise. 411 * 412 */ 413 private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) { 414 if (isAmbiguousProviderList(providerList)) { 415 Reporter.warn("Class path contains multiple SLF4J providers."); 416 for (SLF4JServiceProvider provider : providerList) { 417 Reporter.warn("Found provider [" + provider + "]"); 418 } 419 Reporter.warn("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); 420 } 421 } 422 423 private static void reportActualBinding(List<SLF4JServiceProvider> providerList) { 424 // impossible since a provider has been found 425 if (providerList.isEmpty()) { 426 throw new IllegalStateException("No providers were found which is impossible after successful initialization."); 427 } 428 429 if (isAmbiguousProviderList(providerList)) { 430 Reporter.info("Actual provider is of type [" + providerList.get(0) + "]"); 431 } else { 432 SLF4JServiceProvider provider = providerList.get(0); 433 Reporter.debug(CONNECTED_WITH_MSG + provider.getClass().getName() + "]"); 434 } 435 } 436 437 /** 438 * Return a logger named according to the name parameter using the 439 * statically bound {@link ILoggerFactory} instance. 440 * 441 * @param name 442 * The name of the logger. 443 * @return logger 444 */ 445 public static Logger getLogger(String name) { 446 ILoggerFactory iLoggerFactory = getILoggerFactory(); 447 return iLoggerFactory.getLogger(name); 448 } 449 450 /** 451 * Return a logger named corresponding to the class passed as parameter, 452 * using the statically bound {@link ILoggerFactory} instance. 453 * 454 * <p> 455 * In case the <code>clazz</code> parameter differs from the name of the 456 * caller as computed internally by SLF4J, a logger name mismatch warning 457 * will be printed but only if the 458 * <code>slf4j.detectLoggerNameMismatch</code> system property is set to 459 * true. By default, this property is not set and no warnings will be 460 * printed even in case of a logger name mismatch. 461 * 462 * @param clazz 463 * the returned logger will be named after clazz 464 * @return logger 465 * 466 * 467 * @see <a 468 * href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected 469 * logger name mismatch</a> 470 */ 471 public static Logger getLogger(Class<?> clazz) { 472 Logger logger = getLogger(clazz.getName()); 473 if (DETECT_LOGGER_NAME_MISMATCH) { 474 Class<?> autoComputedCallingClass = Util.getCallingClass(); 475 if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 476 Reporter.warn(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 477 autoComputedCallingClass.getName())); 478 Reporter.warn("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 479 } 480 } 481 return logger; 482 } 483 484 private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) { 485 return !autoComputedCallingClass.isAssignableFrom(clazz); 486 } 487 488 /** 489 * Return the {@link ILoggerFactory} instance in use. 490 * <p> 491 * <p> 492 * ILoggerFactory instance is bound with this class at compile time. 493 * 494 * @return the ILoggerFactory instance in use 495 */ 496 public static ILoggerFactory getILoggerFactory() { 497 return getProvider().getLoggerFactory(); 498 } 499 500 /** 501 * Return the {@link SLF4JServiceProvider} in use. 502 503 * @return provider in use 504 * @since 1.8.0 505 */ 506 static SLF4JServiceProvider getProvider() { 507 if (INITIALIZATION_STATE == UNINITIALIZED) { 508 synchronized (LoggerFactory.class) { 509 if (INITIALIZATION_STATE == UNINITIALIZED) { 510 INITIALIZATION_STATE = ONGOING_INITIALIZATION; 511 performInitialization(); 512 } 513 } 514 } 515 switch (INITIALIZATION_STATE) { 516 case SUCCESSFUL_INITIALIZATION: 517 return PROVIDER; 518 case NOP_FALLBACK_INITIALIZATION: 519 return NOP_FALLBACK_SERVICE_PROVIDER; 520 case FAILED_INITIALIZATION: 521 throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 522 case ONGOING_INITIALIZATION: 523 // support re-entrant behavior. 524 // See also http://jira.qos.ch/browse/SLF4J-97 525 return SUBST_PROVIDER; 526 } 527 throw new IllegalStateException("Unreachable code"); 528 } 529}