• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2004-2011 QOS.ch
3  * All rights reserved.
4  *
5  * Permission is hereby granted, free  of charge, to any person obtaining
6  * a  copy  of this  software  and  associated  documentation files  (the
7  * "Software"), to  deal in  the Software without  restriction, including
8  * without limitation  the rights to  use, copy, modify,  merge, publish,
9  * distribute,  sublicense, and/or sell  copies of  the Software,  and to
10  * permit persons to whom the Software  is furnished to do so, subject to
11  * the following conditions:
12  *
13  * The  above  copyright  notice  and  this permission  notice  shall  be
14  * included in all copies or substantial portions of the Software.
15  *
16  * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
17  * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
18  * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21  * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
22  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  *
24  */
25 package org.slf4j;
26 
27 import java.io.IOException;
28 import java.lang.reflect.Constructor;
29 import java.lang.reflect.InvocationTargetException;
30 import java.net.URL;
31 import java.security.AccessController;
32 import java.security.PrivilegedAction;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Enumeration;
36 import java.util.Iterator;
37 import java.util.LinkedHashSet;
38 import java.util.List;
39 import java.util.ServiceConfigurationError;
40 import java.util.ServiceLoader;
41 import java.util.Set;
42 import java.util.concurrent.LinkedBlockingQueue;
43 
44 import org.slf4j.event.SubstituteLoggingEvent;
45 import org.slf4j.helpers.NOP_FallbackServiceProvider;
46 import org.slf4j.helpers.Reporter;
47 import org.slf4j.helpers.SubstituteLogger;
48 import org.slf4j.helpers.SubstituteServiceProvider;
49 import org.slf4j.helpers.Util;
50 import org.slf4j.spi.SLF4JServiceProvider;
51 
52 /**
53  * The <code>LoggerFactory</code> is a utility class producing Loggers for
54  * various logging APIs, e.g. logback, reload4j, log4j and JDK 1.4 logging.
55  * Other implementations such as {@link org.slf4j.helpers.NOPLogger NOPLogger} and
56  * SimpleLogger are also supported.
57  *
58  * <p><code>LoggerFactory</code>  is essentially a wrapper around an
59  * {@link ILoggerFactory} instance provided by a {@link SLF4JServiceProvider}.
60  *
61  * <p>
62  * Please note that all methods in <code>LoggerFactory</code> are static.
63  *
64  * @author Alexander Dorokhine
65  * @author Robert Elliot
66  * @author Ceki G&uuml;lc&uuml;
67  *
68  */
69 public final class LoggerFactory {
70 
71     static final String CODES_PREFIX = "https://www.slf4j.org/codes.html";
72 
73     static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders";
74     static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings";
75 
76     static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
77     static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
78     static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
79     static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
80     static final String REPLAY_URL = CODES_PREFIX + "#replay";
81 
82     static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
83     static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also "
84                     + UNSUCCESSFUL_INIT_URL;
85     /**
86      * System property for explicitly setting the provider class. If set and the provider could be instantiated,
87      * then the service loading mechanism will be bypassed.
88      *
89      * @since 2.0.9
90      */
91     static final public String PROVIDER_PROPERTY_KEY = "slf4j.provider";
92 
93     static final int UNINITIALIZED = 0;
94     static final int ONGOING_INITIALIZATION = 1;
95     static final int FAILED_INITIALIZATION = 2;
96     static final int SUCCESSFUL_INITIALIZATION = 3;
97     static final int NOP_FALLBACK_INITIALIZATION = 4;
98 
99     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
findServiceProviders()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 
getServiceLoader(final ClassLoader classLoaderOfLoggerFactory)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 
safelyInstantiate(List<SLF4JServiceProvider> providerList, Iterator<SLF4JServiceProvider> iterator)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
LoggerFactory()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      */
reset()178     static void reset() {
179         INITIALIZATION_STATE = UNINITIALIZED;
180     }
181 
performInitialization()182     private final static void performInitialization() {
183         bind();
184         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
185             versionSanityCheck();
186         }
187     }
188 
bind()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 
loadExplicitlySpecified(ClassLoader classLoader)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 
reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet)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 
findPossibleStaticLoggerBinderPathSet()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 
postBindCleanUp()278     private static void postBindCleanUp() {
279         fixSubstituteLoggers();
280         replayEvents();
281         // release all resources in SUBST_FACTORY
282         SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
283     }
284 
fixSubstituteLoggers()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 
failedBinding(Throwable t)296     static void failedBinding(Throwable t) {
297         INITIALIZATION_STATE = FAILED_INITIALIZATION;
298         Reporter.error("Failed to instantiate SLF4J LoggerFactory", t);
299     }
300 
replayEvents()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 
emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize)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 
replaySingleEvent(SubstituteLoggingEvent event)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 
emitSubstitutionWarning()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 
emitReplayWarning(int eventCount)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 
versionSanityCheck()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 
isAmbiguousProviderList(List<SLF4JServiceProvider> providerList)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      */
reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList)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 
reportActualBinding(List<SLF4JServiceProvider> providerList)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      */
getLogger(String name)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      */
getLogger(Class<?> clazz)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 
nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass)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      */
getILoggerFactory()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      */
getProvider()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 }
505