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