001/**
002 * Copyright (c) 2004-2011 QOS.ch
003 * All rights reserved.
004 *
005 * Permission is hereby granted, free  of charge, to any person obtaining
006 * a  copy  of this  software  and  associated  documentation files  (the
007 * "Software"), to  deal in  the Software without  restriction, including
008 * without limitation  the rights to  use, copy, modify,  merge, publish,
009 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
010 * permit persons to whom the Software  is furnished to do so, subject to
011 * the following conditions:
012 *
013 * The  above  copyright  notice  and  this permission  notice  shall  be
014 * included in all copies or substantial portions of the Software.
015 *
016 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
017 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
018 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
021 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023 *
024 */
025package org.slf4j;
026
027import java.io.IOException;
028import java.net.URL;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Enumeration;
032import java.util.LinkedHashSet;
033import java.util.List;
034import java.util.Set;
035import java.util.concurrent.LinkedBlockingQueue;
036
037import org.slf4j.event.SubstituteLoggingEvent;
038import org.slf4j.helpers.NOPLoggerFactory;
039import org.slf4j.helpers.SubstituteLogger;
040import org.slf4j.helpers.SubstituteLoggerFactory;
041import org.slf4j.helpers.Util;
042import org.slf4j.impl.StaticLoggerBinder;
043
044/**
045 * The <code>LoggerFactory</code> is a utility class producing Loggers for
046 * various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
047 * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and
048 * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported.
049 * <p/>
050 * <p/>
051 * <code>LoggerFactory</code> is essentially a wrapper around an
052 * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
053 * compile time.
054 * <p/>
055 * <p/>
056 * Please note that all methods in <code>LoggerFactory</code> are static.
057 * 
058 * 
059 * @author Alexander Dorokhine
060 * @author Robert Elliot
061 * @author Ceki G&uuml;lc&uuml;
062 * 
063 */
064public final class LoggerFactory {
065
066    static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
067
068    static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
069    static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
070    static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
071    static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
072    static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
073    static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
074    static final String REPLAY_URL = CODES_PREFIX + "#replay";
075
076    static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
077    static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also " + UNSUCCESSFUL_INIT_URL;
078
079    static final int UNINITIALIZED = 0;
080    static final int ONGOING_INITIALIZATION = 1;
081    static final int FAILED_INITIALIZATION = 2;
082    static final int SUCCESSFUL_INITIALIZATION = 3;
083    static final int NOP_FALLBACK_INITIALIZATION = 4;
084
085    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
086    static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory();
087    static final NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
088
089    // Support for detecting mismatched logger names.
090    static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
091    static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
092
093    static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
094
095    /**
096     * It is LoggerFactory's responsibility to track version changes and manage
097     * the compatibility list.
098     * <p/>
099     * <p/>
100     * It is assumed that all versions in the 1.6 are mutually compatible.
101     */
102    static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" };
103
104    // private constructor prevents instantiation
105    private LoggerFactory() {
106    }
107
108    /**
109     * Force LoggerFactory to consider itself uninitialized.
110     * <p/>
111     * <p/>
112     * This method is intended to be called by classes (in the same package) for
113     * testing purposes. This method is internal. It can be modified, renamed or
114     * removed at any time without notice.
115     * <p/>
116     * <p/>
117     * You are strongly discouraged from calling this method in production code.
118     */
119    static void reset() {
120        INITIALIZATION_STATE = UNINITIALIZED;
121    }
122
123    private final static void performInitialization() {
124        bind();
125        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
126            versionSanityCheck();
127        }
128    }
129
130    private static boolean messageContainsOrgSlf4jImplStaticLoggerBinder(String msg) {
131        if (msg == null)
132            return false;
133        if (msg.contains("org/slf4j/impl/StaticLoggerBinder"))
134            return true;
135        if (msg.contains("org.slf4j.impl.StaticLoggerBinder"))
136            return true;
137        return false;
138    }
139
140    private final static void bind() {
141        try {
142            Set<URL> staticLoggerBinderPathSet = null;
143            // skip check under android, see also
144            // http://jira.qos.ch/browse/SLF4J-328
145            if (!isAndroid()) {
146                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
147                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
148            }
149            // the next line does the binding
150            StaticLoggerBinder.getSingleton();
151            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
152            reportActualBinding(staticLoggerBinderPathSet);
153            fixSubstituteLoggers();
154            replayEvents();
155            // release all resources in SUBST_FACTORY
156            SUBST_FACTORY.clear();
157        } catch (NoClassDefFoundError ncde) {
158            String msg = ncde.getMessage();
159            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
160                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
161                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
162                Util.report("Defaulting to no-operation (NOP) logger implementation");
163                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
164            } else {
165                failedBinding(ncde);
166                throw ncde;
167            }
168        } catch (java.lang.NoSuchMethodError nsme) {
169            String msg = nsme.getMessage();
170            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
171                INITIALIZATION_STATE = FAILED_INITIALIZATION;
172                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
173                Util.report("Your binding is version 1.5.5 or earlier.");
174                Util.report("Upgrade your binding to version 1.6.x.");
175            }
176            throw nsme;
177        } catch (Exception e) {
178            failedBinding(e);
179            throw new IllegalStateException("Unexpected initialization failure", e);
180        }
181    }
182
183    private static void fixSubstituteLoggers() {
184        synchronized (SUBST_FACTORY) {
185            SUBST_FACTORY.postInitialization();
186            for (SubstituteLogger substLogger : SUBST_FACTORY.getLoggers()) {
187                Logger logger = getLogger(substLogger.getName());
188                substLogger.setDelegate(logger);
189            }
190        }
191
192    }
193
194    static void failedBinding(Throwable t) {
195        INITIALIZATION_STATE = FAILED_INITIALIZATION;
196        Util.report("Failed to instantiate SLF4J LoggerFactory", t);
197    }
198
199    private static void replayEvents() {
200        final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_FACTORY.getEventQueue();
201        final int queueSize = queue.size();
202        int count = 0;
203        final int maxDrain = 128;
204        List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain);
205        while (true) {
206            int numDrained = queue.drainTo(eventList, maxDrain);
207            if (numDrained == 0)
208                break;
209            for (SubstituteLoggingEvent event : eventList) {
210                replaySingleEvent(event);
211                if (count++ == 0)
212                    emitReplayOrSubstituionWarning(event, queueSize);
213            }
214            eventList.clear();
215        }
216    }
217
218    private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
219        if (event.getLogger().isDelegateEventAware()) {
220            emitReplayWarning(queueSize);
221        } else if (event.getLogger().isDelegateNOP()) {
222            // nothing to do
223        } else {
224            emitSubstitutionWarning();
225        }
226    }
227
228    private static void replaySingleEvent(SubstituteLoggingEvent event) {
229        if (event == null)
230            return;
231
232        SubstituteLogger substLogger = event.getLogger();
233        String loggerName = substLogger.getName();
234        if (substLogger.isDelegateNull()) {
235            throw new IllegalStateException("Delegate logger cannot be null at this state.");
236        }
237
238        if (substLogger.isDelegateNOP()) {
239            // nothing to do
240        } else if (substLogger.isDelegateEventAware()) {
241            substLogger.log(event);
242        } else {
243            Util.report(loggerName);
244        }
245    }
246
247    private static void emitSubstitutionWarning() {
248        Util.report("The following set of substitute loggers may have been accessed");
249        Util.report("during the initialization phase. Logging calls during this");
250        Util.report("phase were not honored. However, subsequent logging calls to these");
251        Util.report("loggers will work as normally expected.");
252        Util.report("See also " + SUBSTITUTE_LOGGER_URL);
253    }
254
255    private static void emitReplayWarning(int eventCount) {
256        Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
257        Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
258        Util.report("See also " + REPLAY_URL);
259    }
260
261    private final static void versionSanityCheck() {
262        try {
263            String requested = StaticLoggerBinder.REQUESTED_API_VERSION;
264
265            boolean match = false;
266            for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
267                if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
268                    match = true;
269                }
270            }
271            if (!match) {
272                Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
273                                + Arrays.asList(API_COMPATIBILITY_LIST).toString());
274                Util.report("See " + VERSION_MISMATCH + " for further details.");
275            }
276        } catch (java.lang.NoSuchFieldError nsfe) {
277            // given our large user base and SLF4J's commitment to backward
278            // compatibility, we cannot cry here. Only for implementations
279            // which willingly declare a REQUESTED_API_VERSION field do we
280            // emit compatibility warnings.
281        } catch (Throwable e) {
282            // we should never reach here
283            Util.report("Unexpected problem occured during version sanity check", e);
284        }
285    }
286
287    // We need to use the name of the StaticLoggerBinder class, but we can't
288    // reference
289    // the class itself.
290    private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
291
292    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
293        // use Set instead of list in order to deal with bug #138
294        // LinkedHashSet appropriate here because it preserves insertion order
295        // during iteration
296        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
297        try {
298            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
299            Enumeration<URL> paths;
300            if (loggerFactoryClassLoader == null) {
301                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
302            } else {
303                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
304            }
305            while (paths.hasMoreElements()) {
306                URL path = paths.nextElement();
307                staticLoggerBinderPathSet.add(path);
308            }
309        } catch (IOException ioe) {
310            Util.report("Error getting resources from path", ioe);
311        }
312        return staticLoggerBinderPathSet;
313    }
314
315    private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet) {
316        return binderPathSet.size() > 1;
317    }
318
319    /**
320     * Prints a warning message on the console if multiple bindings were found
321     * on the class path. No reporting is done otherwise.
322     * 
323     */
324    private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
325        if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
326            Util.report("Class path contains multiple SLF4J bindings.");
327            for (URL path : binderPathSet) {
328                Util.report("Found binding in [" + path + "]");
329            }
330            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
331        }
332    }
333
334    private static boolean isAndroid() {
335        String vendor = Util.safeGetSystemProperty(JAVA_VENDOR_PROPERTY);
336        if (vendor == null)
337            return false;
338        return vendor.toLowerCase().contains("android");
339    }
340
341    private static void reportActualBinding(Set<URL> binderPathSet) {
342        // binderPathSet can be null under Android
343        if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
344            Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
345        }
346    }
347
348    /**
349     * Return a logger named according to the name parameter using the
350     * statically bound {@link ILoggerFactory} instance.
351     * 
352     * @param name
353     *            The name of the logger.
354     * @return logger
355     */
356    public static Logger getLogger(String name) {
357        ILoggerFactory iLoggerFactory = getILoggerFactory();
358        return iLoggerFactory.getLogger(name);
359    }
360
361    /**
362     * Return a logger named corresponding to the class passed as parameter,
363     * using the statically bound {@link ILoggerFactory} instance.
364     * 
365     * <p>
366     * In case the the <code>clazz</code> parameter differs from the name of the
367     * caller as computed internally by SLF4J, a logger name mismatch warning
368     * will be printed but only if the
369     * <code>slf4j.detectLoggerNameMismatch</code> system property is set to
370     * true. By default, this property is not set and no warnings will be
371     * printed even in case of a logger name mismatch.
372     * 
373     * @param clazz
374     *            the returned logger will be named after clazz
375     * @return logger
376     * 
377     * 
378     * @see <a
379     *      href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected
380     *      logger name mismatch</a>
381     */
382    public static Logger getLogger(Class<?> clazz) {
383        Logger logger = getLogger(clazz.getName());
384        if (DETECT_LOGGER_NAME_MISMATCH) {
385            Class<?> autoComputedCallingClass = Util.getCallingClass();
386            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
387                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
388                                autoComputedCallingClass.getName()));
389                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
390            }
391        }
392        return logger;
393    }
394
395    private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
396        return !autoComputedCallingClass.isAssignableFrom(clazz);
397    }
398
399    /**
400     * Return the {@link ILoggerFactory} instance in use.
401     * <p/>
402     * <p/>
403     * ILoggerFactory instance is bound with this class at compile time.
404     * 
405     * @return the ILoggerFactory instance in use
406     */
407    public static ILoggerFactory getILoggerFactory() {
408        if (INITIALIZATION_STATE == UNINITIALIZED) {
409            synchronized (LoggerFactory.class) {
410                if (INITIALIZATION_STATE == UNINITIALIZED) {
411                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
412                    performInitialization();
413                }
414            }
415        }
416        switch (INITIALIZATION_STATE) {
417        case SUCCESSFUL_INITIALIZATION:
418            return StaticLoggerBinder.getSingleton().getLoggerFactory();
419        case NOP_FALLBACK_INITIALIZATION:
420            return NOP_FALLBACK_FACTORY;
421        case FAILED_INITIALIZATION:
422            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
423        case ONGOING_INITIALIZATION:
424            // support re-entrant behavior.
425            // See also http://jira.qos.ch/browse/SLF4J-97
426            return SUBST_FACTORY;
427        }
428        throw new IllegalStateException("Unreachable code");
429    }
430}