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