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