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