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.Iterator;
32  import java.util.LinkedHashSet;
33  import java.util.List;
34  import java.util.Set;
35  
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  
73    static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
74    static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory could not be successfully initialized. See also "
75        + 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 TEMP_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 boolean DETECT_LOGGER_NAME_MISMATCH = Boolean.getBoolean(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.indexOf("org/slf4j/impl/StaticLoggerBinder") != -1)
131       return true;
132     if (msg.indexOf("org.slf4j.impl.StaticLoggerBinder") != -1)
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.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
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 (int i = 0; i < API_COMPATIBILITY_LIST.length; i++) {
203         if (requested.startsWith(API_COMPATIBILITY_LIST[i])) {
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   private 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 = (URL) 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 (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
260       Util.report("Class path contains multiple SLF4J bindings.");
261       Iterator<URL> iterator = staticLoggerBinderPathSet.iterator();
262       while (iterator.hasNext()) {
263         URL path = (URL) iterator.next();
264         Util.report("Found binding in [" + path + "]");
265       }
266       Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
267     }
268   }
269 
270   private static void reportActualBinding(Set<URL> staticLoggerBinderPathSet) {
271     if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
272       Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
273     }
274   }
275 
276   /**
277    * Return a logger named according to the name parameter using the statically
278    * bound {@link ILoggerFactory} instance.
279    *
280    * @param name The name of the logger.
281    * @return logger
282    */
283   public static Logger getLogger(String name) {
284     ILoggerFactory iLoggerFactory = getILoggerFactory();
285     return iLoggerFactory.getLogger(name);
286   }
287 
288   /**
289    * Return a logger named corresponding to the class passed as parameter, using
290    * the statically bound {@link ILoggerFactory} instance.
291    *
292    * <p>In case the the <code>clazz</code> parameter differs from the name of
293    * the caller as computed internally by SLF4J, a logger name mismatch warning will be 
294    * printed but only if the <code>slf4j.detectLoggerNameMismatch</code> system property is 
295    * set to true. By default, this property is not set and no warnings will be printed
296    * even in case of a logger name mismatch.
297    * 
298    * @param clazz the returned logger will be named after clazz
299    * @return logger
300    *
301    *
302    * @see <a href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected logger name mismatch</a> 
303    */
304   public static Logger getLogger(Class<?> clazz) {
305     Logger logger = getLogger(clazz.getName());
306     if (DETECT_LOGGER_NAME_MISMATCH) {
307       Class<?> autoComputedCallingClass = Util.getCallingClass();
308       if (nonMatchingClasses(clazz, autoComputedCallingClass)) {
309         Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".",
310             logger.getName(), autoComputedCallingClass.getName()));
311         Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
312       }
313     }
314     return logger;
315   }
316 
317   private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
318     return !autoComputedCallingClass.isAssignableFrom(clazz);
319   }
320 
321   /**
322    * Return the {@link ILoggerFactory} instance in use.
323    * <p/>
324    * <p/>
325    * ILoggerFactory instance is bound with this class at compile time.
326    *
327    * @return the ILoggerFactory instance in use
328    */
329   public static ILoggerFactory getILoggerFactory() {
330     if (INITIALIZATION_STATE == UNINITIALIZED) {
331       INITIALIZATION_STATE = ONGOING_INITIALIZATION;
332       performInitialization();
333     }
334     switch (INITIALIZATION_STATE) {
335     case SUCCESSFUL_INITIALIZATION:
336       return StaticLoggerBinder.getSingleton().getLoggerFactory();
337     case NOP_FALLBACK_INITIALIZATION:
338       return NOP_FALLBACK_FACTORY;
339     case FAILED_INITIALIZATION:
340       throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
341     case ONGOING_INITIALIZATION:
342       // support re-entrant behavior.
343       // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
344       return TEMP_FACTORY;
345     }
346     throw new IllegalStateException("Unreachable code");
347   }
348 }