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