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