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.SubstituteServiceProvider;
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.nop.NOPLogger NOPLogger} and
50   * {@link org.slf4j.simple.SimpleLogger SimpleLogger} are also supported.
51   * 
52   * <p><code>LoggerFactory</code> is essentially a wrapper around an
53   * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
54   * compile time.
55   * 
56   * <p>
57   * Please note that all methods in <code>LoggerFactory</code> are static.
58   * 
59   * @author Alexander Dorokhine
60   * @author Robert Elliot
61   * @author Ceki G&uuml;lc&uuml;
62   * 
63   */
64  public final class LoggerFactory {
65  
66      static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
67  
68      static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders";
69      static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings";
70  
71      static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
72      static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
73      static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
74      static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
75      static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
76      static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
77      static final String REPLAY_URL = CODES_PREFIX + "#replay";
78  
79      static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
80      static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also "
81                      + UNSUCCESSFUL_INIT_URL;
82  
83      static final int UNINITIALIZED = 0;
84      static final int ONGOING_INITIALIZATION = 1;
85      static final int FAILED_INITIALIZATION = 2;
86      static final int SUCCESSFUL_INITIALIZATION = 3;
87      static final int NOP_FALLBACK_INITIALIZATION = 4;
88  
89      static volatile int INITIALIZATION_STATE = UNINITIALIZED;
90      static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider();
91      static final NOPServiceProvider NOP_FALLBACK_FACTORY = new NOPServiceProvider();
92  
93      // Support for detecting mismatched logger names.
94      static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
95      static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
96  
97      static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
98  
99      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<SLF4JServiceProvider>();
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[] { "1.8", "1.7" };
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 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<URL>();
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<SubstituteLoggingEvent>(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.getRequesteApiVersion();
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_FACTORY;
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 }