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.net.URL; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Enumeration; 032import java.util.LinkedHashSet; 033import java.util.List; 034import java.util.ServiceLoader; 035import java.util.Set; 036import java.util.concurrent.LinkedBlockingQueue; 037 038import org.slf4j.event.SubstituteLoggingEvent; 039import org.slf4j.helpers.NOP_FallbackServiceProvider; 040import org.slf4j.helpers.SubstituteServiceProvider; 041import org.slf4j.helpers.SubstituteLogger; 042 043import org.slf4j.helpers.Util; 044import org.slf4j.spi.SLF4JServiceProvider; 045 046/** 047 * The <code>LoggerFactory</code> is a utility class producing Loggers for 048 * various logging APIs, most notably for log4j, logback and JDK 1.4 logging. 049 * Other implementations such as {@link org.slf4j.helpers.NOPLogger NOPLogger} and 050 * SimpleLogger are also supported. 051 * 052 * <p><code>LoggerFactory</code> is essentially a wrapper around an 053 * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at 054 * compile time. 055 * 056 * <p> 057 * Please note that all methods in <code>LoggerFactory</code> are static. 058 * 059 * @author Alexander Dorokhine 060 * @author Robert Elliot 061 * @author Ceki Gülcü 062 * 063 */ 064public final class LoggerFactory { 065 066 static final String CODES_PREFIX = "https://www.slf4j.org/codes.html"; 067 068 static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders"; 069 static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings"; 070 071 static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder"; 072 static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings"; 073 static final String NULL_LF_URL = CODES_PREFIX + "#null_LF"; 074 static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch"; 075 static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger"; 076 static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch"; 077 static final String REPLAY_URL = CODES_PREFIX + "#replay"; 078 079 static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit"; 080 static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also " 081 + UNSUCCESSFUL_INIT_URL; 082 083 static final int UNINITIALIZED = 0; 084 static final int ONGOING_INITIALIZATION = 1; 085 static final int FAILED_INITIALIZATION = 2; 086 static final int SUCCESSFUL_INITIALIZATION = 3; 087 static final int NOP_FALLBACK_INITIALIZATION = 4; 088 089 static volatile int INITIALIZATION_STATE = UNINITIALIZED; 090 static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider(); 091 static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider(); 092 093 // Support for detecting mismatched logger names. 094 static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch"; 095 static final String JAVA_VENDOR_PROPERTY = "java.vendor.url"; 096 097 static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY); 098 099 static volatile SLF4JServiceProvider PROVIDER; 100 101 private static List<SLF4JServiceProvider> findServiceProviders() { 102 ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); 103 List<SLF4JServiceProvider> providerList = new ArrayList<>(); 104 for (SLF4JServiceProvider provider : serviceLoader) { 105 providerList.add(provider); 106 } 107 return providerList; 108 } 109 110 /** 111 * It is LoggerFactory's responsibility to track version changes and manage 112 * the compatibility list. 113 * <p> 114 * <p> 115 * It is assumed that all versions in the 1.6 are mutually compatible. 116 */ 117 static private final String[] API_COMPATIBILITY_LIST = new String[] { "2.0" }; 118 119 // private constructor prevents instantiation 120 private LoggerFactory() { 121 } 122 123 /** 124 * Force LoggerFactory to consider itself uninitialized. 125 * <p> 126 * <p> 127 * This method is intended to be called by classes (in the same package) for 128 * testing purposes. This method is internal. It can be modified, renamed or 129 * removed at any time without notice. 130 * <p> 131 * <p> 132 * You are strongly discouraged from calling this method in production code. 133 */ 134 static void reset() { 135 INITIALIZATION_STATE = UNINITIALIZED; 136 } 137 138 private final static void performInitialization() { 139 bind(); 140 if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 141 versionSanityCheck(); 142 } 143 } 144 145 private final static void bind() { 146 try { 147 List<SLF4JServiceProvider> providersList = findServiceProviders(); 148 reportMultipleBindingAmbiguity(providersList); 149 if (providersList != null && !providersList.isEmpty()) { 150 PROVIDER = providersList.get(0); 151 // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else. 152 PROVIDER.initialize(); 153 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 154 reportActualBinding(providersList); 155 } else { 156 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; 157 Util.report("No SLF4J providers were found."); 158 Util.report("Defaulting to no-operation (NOP) logger implementation"); 159 Util.report("See " + NO_PROVIDERS_URL + " for further details."); 160 161 Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 162 reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet); 163 } 164 postBindCleanUp(); 165 } catch (Exception e) { 166 failedBinding(e); 167 throw new IllegalStateException("Unexpected initialization failure", e); 168 } 169 } 170 171 private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) { 172 if (staticLoggerBinderPathSet.isEmpty()) { 173 return; 174 } 175 Util.report("Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier."); 176 177 for (URL path : staticLoggerBinderPathSet) { 178 Util.report("Ignoring binding found at [" + path + "]"); 179 } 180 Util.report("See " + IGNORED_BINDINGS_URL + " for an explanation."); 181 182 } 183 184 // We need to use the name of the StaticLoggerBinder class, but we can't 185 // reference the class itself. 186 private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; 187 188 static Set<URL> findPossibleStaticLoggerBinderPathSet() { 189 // use Set instead of list in order to deal with bug #138 190 // LinkedHashSet appropriate here because it preserves insertion order 191 // during iteration 192 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>(); 193 try { 194 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); 195 Enumeration<URL> paths; 196 if (loggerFactoryClassLoader == null) { 197 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 198 } else { 199 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 200 } 201 while (paths.hasMoreElements()) { 202 URL path = paths.nextElement(); 203 staticLoggerBinderPathSet.add(path); 204 } 205 } catch (IOException ioe) { 206 Util.report("Error getting resources from path", ioe); 207 } 208 return staticLoggerBinderPathSet; 209 } 210 211 private static void postBindCleanUp() { 212 fixSubstituteLoggers(); 213 replayEvents(); 214 // release all resources in SUBST_FACTORY 215 SUBST_PROVIDER.getSubstituteLoggerFactory().clear(); 216 } 217 218 private static void fixSubstituteLoggers() { 219 synchronized (SUBST_PROVIDER) { 220 SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization(); 221 for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) { 222 Logger logger = getLogger(substLogger.getName()); 223 substLogger.setDelegate(logger); 224 } 225 } 226 227 } 228 229 static void failedBinding(Throwable t) { 230 INITIALIZATION_STATE = FAILED_INITIALIZATION; 231 Util.report("Failed to instantiate SLF4J LoggerFactory", t); 232 } 233 234 private static void replayEvents() { 235 final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue(); 236 final int queueSize = queue.size(); 237 int count = 0; 238 final int maxDrain = 128; 239 List<SubstituteLoggingEvent> eventList = new ArrayList<>(maxDrain); 240 while (true) { 241 int numDrained = queue.drainTo(eventList, maxDrain); 242 if (numDrained == 0) 243 break; 244 for (SubstituteLoggingEvent event : eventList) { 245 replaySingleEvent(event); 246 if (count++ == 0) 247 emitReplayOrSubstituionWarning(event, queueSize); 248 } 249 eventList.clear(); 250 } 251 } 252 253 private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) { 254 if (event.getLogger().isDelegateEventAware()) { 255 emitReplayWarning(queueSize); 256 } else if (event.getLogger().isDelegateNOP()) { 257 // nothing to do 258 } else { 259 emitSubstitutionWarning(); 260 } 261 } 262 263 private static void replaySingleEvent(SubstituteLoggingEvent event) { 264 if (event == null) 265 return; 266 267 SubstituteLogger substLogger = event.getLogger(); 268 String loggerName = substLogger.getName(); 269 if (substLogger.isDelegateNull()) { 270 throw new IllegalStateException("Delegate logger cannot be null at this state."); 271 } 272 273 if (substLogger.isDelegateNOP()) { 274 // nothing to do 275 } else if (substLogger.isDelegateEventAware()) { 276 substLogger.log(event); 277 } else { 278 Util.report(loggerName); 279 } 280 } 281 282 private static void emitSubstitutionWarning() { 283 Util.report("The following set of substitute loggers may have been accessed"); 284 Util.report("during the initialization phase. Logging calls during this"); 285 Util.report("phase were not honored. However, subsequent logging calls to these"); 286 Util.report("loggers will work as normally expected."); 287 Util.report("See also " + SUBSTITUTE_LOGGER_URL); 288 } 289 290 private static void emitReplayWarning(int eventCount) { 291 Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are"); 292 Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system."); 293 Util.report("See also " + REPLAY_URL); 294 } 295 296 private final static void versionSanityCheck() { 297 try { 298 String requested = PROVIDER.getRequestedApiVersion(); 299 300 boolean match = false; 301 for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) { 302 if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) { 303 match = true; 304 } 305 } 306 if (!match) { 307 Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " 308 + Arrays.asList(API_COMPATIBILITY_LIST).toString()); 309 Util.report("See " + VERSION_MISMATCH + " for further details."); 310 } 311 } catch (java.lang.NoSuchFieldError nsfe) { 312 // given our large user base and SLF4J's commitment to backward 313 // compatibility, we cannot cry here. Only for implementations 314 // which willingly declare a REQUESTED_API_VERSION field do we 315 // emit compatibility warnings. 316 } catch (Throwable e) { 317 // we should never reach here 318 Util.report("Unexpected problem occured during version sanity check", e); 319 } 320 } 321 322 private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) { 323 return providerList.size() > 1; 324 } 325 326 /** 327 * Prints a warning message on the console if multiple bindings were found 328 * on the class path. No reporting is done otherwise. 329 * 330 */ 331 private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) { 332 if (isAmbiguousProviderList(providerList)) { 333 Util.report("Class path contains multiple SLF4J providers."); 334 for (SLF4JServiceProvider provider : providerList) { 335 Util.report("Found provider [" + provider + "]"); 336 } 337 Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); 338 } 339 } 340 341 private static void reportActualBinding(List<SLF4JServiceProvider> providerList) { 342 // binderPathSet can be null under Android 343 if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) { 344 Util.report("Actual provider is of type [" + providerList.get(0) + "]"); 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 */ 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 <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 */ 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 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 */ 407 public static ILoggerFactory getILoggerFactory() { 408 return getProvider().getLoggerFactory(); 409 } 410 411 /** 412 * Return the {@link SLF4JServiceProvider} in use. 413 414 * @return provider in use 415 * @since 1.8.0 416 */ 417 static SLF4JServiceProvider getProvider() { 418 if (INITIALIZATION_STATE == UNINITIALIZED) { 419 synchronized (LoggerFactory.class) { 420 if (INITIALIZATION_STATE == UNINITIALIZED) { 421 INITIALIZATION_STATE = ONGOING_INITIALIZATION; 422 performInitialization(); 423 } 424 } 425 } 426 switch (INITIALIZATION_STATE) { 427 case SUCCESSFUL_INITIALIZATION: 428 return PROVIDER; 429 case NOP_FALLBACK_INITIALIZATION: 430 return NOP_FALLBACK_SERVICE_PROVIDER; 431 case FAILED_INITIALIZATION: 432 throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 433 case ONGOING_INITIALIZATION: 434 // support re-entrant behavior. 435 // See also http://jira.qos.ch/browse/SLF4J-97 436 return SUBST_PROVIDER; 437 } 438 throw new IllegalStateException("Unreachable code"); 439 } 440}