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 = "http://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 prior to 1.8."); 176 for (URL path : staticLoggerBinderPathSet) { 177 Util.report("Ignoring binding found at [" + path + "]"); 178 } 179 Util.report("See " + IGNORED_BINDINGS_URL + " for an explanation."); 180 181 } 182 183 // We need to use the name of the StaticLoggerBinder class, but we can't 184 // reference the class itself. 185 private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; 186 187 static Set<URL> findPossibleStaticLoggerBinderPathSet() { 188 // use Set instead of list in order to deal with bug #138 189 // LinkedHashSet appropriate here because it preserves insertion order 190 // during iteration 191 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>(); 192 try { 193 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); 194 Enumeration<URL> paths; 195 if (loggerFactoryClassLoader == null) { 196 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 197 } else { 198 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 199 } 200 while (paths.hasMoreElements()) { 201 URL path = paths.nextElement(); 202 staticLoggerBinderPathSet.add(path); 203 } 204 } catch (IOException ioe) { 205 Util.report("Error getting resources from path", ioe); 206 } 207 return staticLoggerBinderPathSet; 208 } 209 210 private static void postBindCleanUp() { 211 fixSubstituteLoggers(); 212 replayEvents(); 213 // release all resources in SUBST_FACTORY 214 SUBST_PROVIDER.getSubstituteLoggerFactory().clear(); 215 } 216 217 private static void fixSubstituteLoggers() { 218 synchronized (SUBST_PROVIDER) { 219 SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization(); 220 for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) { 221 Logger logger = getLogger(substLogger.getName()); 222 substLogger.setDelegate(logger); 223 } 224 } 225 226 } 227 228 static void failedBinding(Throwable t) { 229 INITIALIZATION_STATE = FAILED_INITIALIZATION; 230 Util.report("Failed to instantiate SLF4J LoggerFactory", t); 231 } 232 233 private static void replayEvents() { 234 final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue(); 235 final int queueSize = queue.size(); 236 int count = 0; 237 final int maxDrain = 128; 238 List<SubstituteLoggingEvent> eventList = new ArrayList<>(maxDrain); 239 while (true) { 240 int numDrained = queue.drainTo(eventList, maxDrain); 241 if (numDrained == 0) 242 break; 243 for (SubstituteLoggingEvent event : eventList) { 244 replaySingleEvent(event); 245 if (count++ == 0) 246 emitReplayOrSubstituionWarning(event, queueSize); 247 } 248 eventList.clear(); 249 } 250 } 251 252 private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) { 253 if (event.getLogger().isDelegateEventAware()) { 254 emitReplayWarning(queueSize); 255 } else if (event.getLogger().isDelegateNOP()) { 256 // nothing to do 257 } else { 258 emitSubstitutionWarning(); 259 } 260 } 261 262 private static void replaySingleEvent(SubstituteLoggingEvent event) { 263 if (event == null) 264 return; 265 266 SubstituteLogger substLogger = event.getLogger(); 267 String loggerName = substLogger.getName(); 268 if (substLogger.isDelegateNull()) { 269 throw new IllegalStateException("Delegate logger cannot be null at this state."); 270 } 271 272 if (substLogger.isDelegateNOP()) { 273 // nothing to do 274 } else if (substLogger.isDelegateEventAware()) { 275 substLogger.log(event); 276 } else { 277 Util.report(loggerName); 278 } 279 } 280 281 private static void emitSubstitutionWarning() { 282 Util.report("The following set of substitute loggers may have been accessed"); 283 Util.report("during the initialization phase. Logging calls during this"); 284 Util.report("phase were not honored. However, subsequent logging calls to these"); 285 Util.report("loggers will work as normally expected."); 286 Util.report("See also " + SUBSTITUTE_LOGGER_URL); 287 } 288 289 private static void emitReplayWarning(int eventCount) { 290 Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are"); 291 Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system."); 292 Util.report("See also " + REPLAY_URL); 293 } 294 295 private final static void versionSanityCheck() { 296 try { 297 String requested = PROVIDER.getRequestedApiVersion(); 298 299 boolean match = false; 300 for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) { 301 if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) { 302 match = true; 303 } 304 } 305 if (!match) { 306 Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " 307 + Arrays.asList(API_COMPATIBILITY_LIST).toString()); 308 Util.report("See " + VERSION_MISMATCH + " for further details."); 309 } 310 } catch (java.lang.NoSuchFieldError nsfe) { 311 // given our large user base and SLF4J's commitment to backward 312 // compatibility, we cannot cry here. Only for implementations 313 // which willingly declare a REQUESTED_API_VERSION field do we 314 // emit compatibility warnings. 315 } catch (Throwable e) { 316 // we should never reach here 317 Util.report("Unexpected problem occured during version sanity check", e); 318 } 319 } 320 321 private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) { 322 return providerList.size() > 1; 323 } 324 325 /** 326 * Prints a warning message on the console if multiple bindings were found 327 * on the class path. No reporting is done otherwise. 328 * 329 */ 330 private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) { 331 if (isAmbiguousProviderList(providerList)) { 332 Util.report("Class path contains multiple SLF4J providers."); 333 for (SLF4JServiceProvider provider : providerList) { 334 Util.report("Found provider [" + provider + "]"); 335 } 336 Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); 337 } 338 } 339 340 private static void reportActualBinding(List<SLF4JServiceProvider> providerList) { 341 // binderPathSet can be null under Android 342 if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) { 343 Util.report("Actual provider is of type [" + providerList.get(0) + "]"); 344 } 345 } 346 347 /** 348 * Return a logger named according to the name parameter using the 349 * statically bound {@link ILoggerFactory} instance. 350 * 351 * @param name 352 * The name of the logger. 353 * @return logger 354 */ 355 public static Logger getLogger(String name) { 356 ILoggerFactory iLoggerFactory = getILoggerFactory(); 357 return iLoggerFactory.getLogger(name); 358 } 359 360 /** 361 * Return a logger named corresponding to the class passed as parameter, 362 * using the statically bound {@link ILoggerFactory} instance. 363 * 364 * <p> 365 * In case the the <code>clazz</code> parameter differs from the name of the 366 * caller as computed internally by SLF4J, a logger name mismatch warning 367 * will be printed but only if the 368 * <code>slf4j.detectLoggerNameMismatch</code> system property is set to 369 * true. By default, this property is not set and no warnings will be 370 * printed even in case of a logger name mismatch. 371 * 372 * @param clazz 373 * the returned logger will be named after clazz 374 * @return logger 375 * 376 * 377 * @see <a 378 * href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected 379 * logger name mismatch</a> 380 */ 381 public static Logger getLogger(Class<?> clazz) { 382 Logger logger = getLogger(clazz.getName()); 383 if (DETECT_LOGGER_NAME_MISMATCH) { 384 Class<?> autoComputedCallingClass = Util.getCallingClass(); 385 if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 386 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 387 autoComputedCallingClass.getName())); 388 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 389 } 390 } 391 return logger; 392 } 393 394 private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) { 395 return !autoComputedCallingClass.isAssignableFrom(clazz); 396 } 397 398 /** 399 * Return the {@link ILoggerFactory} instance in use. 400 * <p> 401 * <p> 402 * ILoggerFactory instance is bound with this class at compile time. 403 * 404 * @return the ILoggerFactory instance in use 405 */ 406 public static ILoggerFactory getILoggerFactory() { 407 return getProvider().getLoggerFactory(); 408 } 409 410 /** 411 * Return the {@link SLF4JServiceProvider} in use. 412 413 * @return provider in use 414 * @since 1.8.0 415 */ 416 static SLF4JServiceProvider getProvider() { 417 if (INITIALIZATION_STATE == UNINITIALIZED) { 418 synchronized (LoggerFactory.class) { 419 if (INITIALIZATION_STATE == UNINITIALIZED) { 420 INITIALIZATION_STATE = ONGOING_INITIALIZATION; 421 performInitialization(); 422 } 423 } 424 } 425 switch (INITIALIZATION_STATE) { 426 case SUCCESSFUL_INITIALIZATION: 427 return PROVIDER; 428 case NOP_FALLBACK_INITIALIZATION: 429 return NOP_FALLBACK_SERVICE_PROVIDER; 430 case FAILED_INITIALIZATION: 431 throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 432 case ONGOING_INITIALIZATION: 433 // support re-entrant behavior. 434 // See also http://jira.qos.ch/browse/SLF4J-97 435 return SUBST_PROVIDER; 436 } 437 throw new IllegalStateException("Unreachable code"); 438 } 439}