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