View Javadoc

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.net.URL;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Enumeration;
32  import java.util.LinkedHashSet;
33  import java.util.List;
34  import java.util.ServiceLoader;
35  import java.util.Set;
36  import java.util.concurrent.LinkedBlockingQueue;
37  
38  import org.slf4j.event.SubstituteLoggingEvent;
39  import org.slf4j.helpers.NOPServiceProvider;
40  import org.slf4j.helpers.SubstitureServiceProvider;
41  import org.slf4j.helpers.SubstituteLogger;
42  
43  import org.slf4j.helpers.Util;
44  import org.slf4j.spi.SLF4JServiceProvider;
45  
46  /**
47   * The <code>LoggerFactory</code> is a utility class producing Loggers for
48   * various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
49   * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and
50   * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported.
51   * <p/>
52   * <p/>
53   * <code>LoggerFactory</code> is essentially a wrapper around an
54   * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
55   * compile time.
56   * <p/>
57   * <p/>
58   * Please note that all methods in <code>LoggerFactory</code> are static.
59   * 
60   * 
61   * @author Alexander Dorokhine
62   * @author Robert Elliot
63   * @author Ceki G&uuml;lc&uuml;
64   * 
65   */
66  public final class LoggerFactory {
67  
68      static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
69  
70      static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders";
71      static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings";
72      
73      static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
74      static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
75      static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
76      static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
77      static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
78      static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
79      static final String REPLAY_URL = CODES_PREFIX + "#replay";
80  
81      static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
82      static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also "
83                      + UNSUCCESSFUL_INIT_URL;
84  
85      static final int UNINITIALIZED = 0;
86      static final int ONGOING_INITIALIZATION = 1;
87      static final int FAILED_INITIALIZATION = 2;
88      static final int SUCCESSFUL_INITIALIZATION = 3;
89      static final int NOP_FALLBACK_INITIALIZATION = 4;
90  
91      static volatile int INITIALIZATION_STATE = UNINITIALIZED;
92      static final SubstitureServiceProvider SUBST_PROVIDER = new SubstitureServiceProvider();
93      static final NOPServiceProvider NOP_FALLBACK_FACTORY = new NOPServiceProvider();
94  
95      // Support for detecting mismatched logger names.
96      static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
97      static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
98  
99      static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
100 
101     static volatile SLF4JServiceProvider PROVIDER;
102 
103     private static List<SLF4JServiceProvider> findServiceProviders() {
104         ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
105         List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
106         for (SLF4JServiceProvider provider : serviceLoader) {
107             providerList.add(provider);
108         }
109         return providerList;
110     }
111 
112     /**
113      * It is LoggerFactory's responsibility to track version changes and manage
114      * the compatibility list.
115      * <p/>
116      * <p/>
117      * It is assumed that all versions in the 1.6 are mutually compatible.
118      */
119     static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.8", "1.7" };
120 
121     // private constructor prevents instantiation
122     private LoggerFactory() {
123     }
124 
125     /**
126      * Force LoggerFactory to consider itself uninitialized.
127      * <p/>
128      * <p/>
129      * This method is intended to be called by classes (in the same package) for
130      * testing purposes. This method is internal. It can be modified, renamed or
131      * removed at any time without notice.
132      * <p/>
133      * <p/>
134      * You are strongly discouraged from calling this method in production code.
135      */
136     static void reset() {
137         INITIALIZATION_STATE = UNINITIALIZED;
138     }
139 
140     private final static void performInitialization() {
141         bind();
142         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
143             versionSanityCheck();
144         }
145     }
146 
147     private final static void bind() {
148         try {
149             List<SLF4JServiceProvider> providersList = findServiceProviders();
150             reportMultipleBindingAmbiguity(providersList);
151             if (providersList != null && !providersList.isEmpty()) {
152             	PROVIDER = providersList.get(0);
153             	PROVIDER.initialize();
154             	INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
155                 reportActualBinding(providersList);
156                 fixSubstituteLoggers();
157                 replayEvents();
158                 // release all resources in SUBST_FACTORY
159                 SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
160             } else {
161                 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
162                 Util.report("No SLF4J providers were found.");
163                 Util.report("Defaulting to no-operation (NOP) logger implementation");
164                 Util.report("See " + NO_PROVIDERS_URL + " for further details.");
165 
166                 Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
167                 reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
168             }
169         } catch (Exception e) {
170             failedBinding(e);
171             throw new IllegalStateException("Unexpected initialization failure", e);
172         }
173     }
174 
175     private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) {
176         if (staticLoggerBinderPathSet.isEmpty()) {
177             return;
178         }
179         Util.report("Class path contains SLF4J bindings targeting slf4j-api versions prior to 1.8.");
180         for (URL path : staticLoggerBinderPathSet) {
181             Util.report("Ignoring binding found at [" + path + "]");
182         }
183         Util.report("See " + IGNORED_BINDINGS_URL + " for an explanation.");
184    
185 
186     }
187 
188     // We need to use the name of the StaticLoggerBinder class, but we can't
189     // reference the class itself.
190     private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
191 
192     static Set<URL> findPossibleStaticLoggerBinderPathSet() {
193         // use Set instead of list in order to deal with bug #138
194         // LinkedHashSet appropriate here because it preserves insertion order
195         // during iteration
196         Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
197         try {
198             ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
199             Enumeration<URL> paths;
200             if (loggerFactoryClassLoader == null) {
201                 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
202             } else {
203                 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
204             }
205             while (paths.hasMoreElements()) {
206                 URL path = paths.nextElement();
207                 staticLoggerBinderPathSet.add(path);
208             }
209         } catch (IOException ioe) {
210             Util.report("Error getting resources from path", ioe);
211         }
212         return staticLoggerBinderPathSet;
213     }
214 
215     private static void fixSubstituteLoggers() {
216         synchronized (SUBST_PROVIDER) {
217             SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization();
218             for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) {
219                 Logger logger = getLogger(substLogger.getName());
220                 substLogger.setDelegate(logger);
221             }
222         }
223 
224     }
225 
226     static void failedBinding(Throwable t) {
227         INITIALIZATION_STATE = FAILED_INITIALIZATION;
228         Util.report("Failed to instantiate SLF4J LoggerFactory", t);
229     }
230 
231     private static void replayEvents() {
232         final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue();
233         final int queueSize = queue.size();
234         int count = 0;
235         final int maxDrain = 128;
236         List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain);
237         while (true) {
238             int numDrained = queue.drainTo(eventList, maxDrain);
239             if (numDrained == 0)
240                 break;
241             for (SubstituteLoggingEvent event : eventList) {
242                 replaySingleEvent(event);
243                 if (count++ == 0)
244                     emitReplayOrSubstituionWarning(event, queueSize);
245             }
246             eventList.clear();
247         }
248     }
249 
250     private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
251         if (event.getLogger().isDelegateEventAware()) {
252             emitReplayWarning(queueSize);
253         } else if (event.getLogger().isDelegateNOP()) {
254             // nothing to do
255         } else {
256             emitSubstitutionWarning();
257         }
258     }
259 
260     private static void replaySingleEvent(SubstituteLoggingEvent event) {
261         if (event == null)
262             return;
263 
264         SubstituteLogger substLogger = event.getLogger();
265         String loggerName = substLogger.getName();
266         if (substLogger.isDelegateNull()) {
267             throw new IllegalStateException("Delegate logger cannot be null at this state.");
268         }
269 
270         if (substLogger.isDelegateNOP()) {
271             // nothing to do
272         } else if (substLogger.isDelegateEventAware()) {
273             substLogger.log(event);
274         } else {
275             Util.report(loggerName);
276         }
277     }
278 
279     private static void emitSubstitutionWarning() {
280         Util.report("The following set of substitute loggers may have been accessed");
281         Util.report("during the initialization phase. Logging calls during this");
282         Util.report("phase were not honored. However, subsequent logging calls to these");
283         Util.report("loggers will work as normally expected.");
284         Util.report("See also " + SUBSTITUTE_LOGGER_URL);
285     }
286 
287     private static void emitReplayWarning(int eventCount) {
288         Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
289         Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
290         Util.report("See also " + REPLAY_URL);
291     }
292 
293     private final static void versionSanityCheck() {
294         try {
295             String requested = PROVIDER.getRequesteApiVersion();
296 
297             boolean match = false;
298             for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
299                 if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
300                     match = true;
301                 }
302             }
303             if (!match) {
304                 Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
305                                 + Arrays.asList(API_COMPATIBILITY_LIST).toString());
306                 Util.report("See " + VERSION_MISMATCH + " for further details.");
307             }
308         } catch (java.lang.NoSuchFieldError nsfe) {
309             // given our large user base and SLF4J's commitment to backward
310             // compatibility, we cannot cry here. Only for implementations
311             // which willingly declare a REQUESTED_API_VERSION field do we
312             // emit compatibility warnings.
313         } catch (Throwable e) {
314             // we should never reach here
315             Util.report("Unexpected problem occured during version sanity check", e);
316         }
317     }
318 
319     private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) {
320         return providerList.size() > 1;
321     }
322 
323     /**
324      * Prints a warning message on the console if multiple bindings were found
325      * on the class path. No reporting is done otherwise.
326      * 
327      */
328     private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) {
329         if (isAmbiguousProviderList(providerList)) {
330             Util.report("Class path contains multiple SLF4J providers.");
331             for (SLF4JServiceProvider provider : providerList) {
332                 Util.report("Found provider [" + provider + "]");
333             }
334             Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
335         }
336     }
337 
338     private static void reportActualBinding(List<SLF4JServiceProvider> providerList) {
339         // binderPathSet can be null under Android
340         if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) {
341             Util.report("Actual provider is of type [" + providerList.get(0) + "]");
342         }
343     }
344 
345     /**
346      * Return a logger named according to the name parameter using the
347      * statically bound {@link ILoggerFactory} instance.
348      * 
349      * @param name
350      *            The name of the logger.
351      * @return logger
352      */
353     public static Logger getLogger(String name) {
354         ILoggerFactory iLoggerFactory = getILoggerFactory();
355         return iLoggerFactory.getLogger(name);
356     }
357 
358     /**
359      * Return a logger named corresponding to the class passed as parameter,
360      * using the statically bound {@link ILoggerFactory} instance.
361      * 
362      * <p>
363      * In case the the <code>clazz</code> parameter differs from the name of the
364      * caller as computed internally by SLF4J, a logger name mismatch warning
365      * will be printed but only if the
366      * <code>slf4j.detectLoggerNameMismatch</code> system property is set to
367      * true. By default, this property is not set and no warnings will be
368      * printed even in case of a logger name mismatch.
369      * 
370      * @param clazz
371      *            the returned logger will be named after clazz
372      * @return logger
373      * 
374      * 
375      * @see <a
376      *      href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected
377      *      logger name mismatch</a>
378      */
379     public static Logger getLogger(Class<?> clazz) {
380         Logger logger = getLogger(clazz.getName());
381         if (DETECT_LOGGER_NAME_MISMATCH) {
382             Class<?> autoComputedCallingClass = Util.getCallingClass();
383             if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
384                 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
385                                 autoComputedCallingClass.getName()));
386                 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
387             }
388         }
389         return logger;
390     }
391 
392     private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
393         return !autoComputedCallingClass.isAssignableFrom(clazz);
394     }
395 
396     /**
397      * Return the {@link ILoggerFactory} instance in use.
398      * <p/>
399      * <p/>
400      * ILoggerFactory instance is bound with this class at compile time.
401      * 
402      * @return the ILoggerFactory instance in use
403      */
404     public static ILoggerFactory getILoggerFactory() {
405         return getProvider().getLoggerFactory();
406     }
407 
408     static SLF4JServiceProvider getProvider() {
409         if (INITIALIZATION_STATE == UNINITIALIZED) {
410             synchronized (LoggerFactory.class) {
411                 if (INITIALIZATION_STATE == UNINITIALIZED) {
412                     INITIALIZATION_STATE = ONGOING_INITIALIZATION;
413                     performInitialization();
414                 }
415             }
416         }
417         switch (INITIALIZATION_STATE) {
418         case SUCCESSFUL_INITIALIZATION:
419             return PROVIDER;
420         case NOP_FALLBACK_INITIALIZATION:
421             return NOP_FALLBACK_FACTORY;
422         case FAILED_INITIALIZATION:
423             throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
424         case ONGOING_INITIALIZATION:
425             // support re-entrant behavior.
426             // See also http://jira.qos.ch/browse/SLF4J-97
427             return SUBST_PROVIDER;
428         }
429         throw new IllegalStateException("Unreachable code");
430     }
431 }