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