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.ServiceLoader;
035import java.util.Set;
036import java.util.concurrent.LinkedBlockingQueue;
037
038import org.slf4j.event.SubstituteLoggingEvent;
039import org.slf4j.helpers.NOPServiceProvider;
040import org.slf4j.helpers.SubstituteServiceProvider;
041import org.slf4j.helpers.SubstituteLogger;
042
043import org.slf4j.helpers.Util;
044import org.slf4j.spi.SLF4JServiceProvider;
045
046/**
047 * The <code>LoggerFactory</code> is a utility class producing Loggers for
048 * various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
049 * Other implementations such as {@link org.slf4j.nop.NOPLogger NOPLogger} and
050 * {@link org.slf4j.simple.SimpleLogger SimpleLogger} are also supported.
051 * 
052 * <p><code>LoggerFactory</code> is essentially a wrapper around an
053 * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
054 * compile time.
055 * 
056 * <p>
057 * Please note that all methods in <code>LoggerFactory</code> are static.
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_PROVIDERS_URL = CODES_PREFIX + "#noProviders";
069    static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings";
070    
071    static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
072    static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
073    static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
074    static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
075    static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
076    static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
077    static final String REPLAY_URL = CODES_PREFIX + "#replay";
078
079    static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
080    static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also "
081                    + UNSUCCESSFUL_INIT_URL;
082
083    static final int UNINITIALIZED = 0;
084    static final int ONGOING_INITIALIZATION = 1;
085    static final int FAILED_INITIALIZATION = 2;
086    static final int SUCCESSFUL_INITIALIZATION = 3;
087    static final int NOP_FALLBACK_INITIALIZATION = 4;
088
089    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
090    static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider();
091    static final NOPServiceProvider NOP_FALLBACK_FACTORY = new NOPServiceProvider();
092
093    // Support for detecting mismatched logger names.
094    static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
095    static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
096
097    static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
098
099    static volatile SLF4JServiceProvider PROVIDER;
100
101    private static List<SLF4JServiceProvider> findServiceProviders() {
102        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
103        List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
104        for (SLF4JServiceProvider provider : serviceLoader) {
105            providerList.add(provider);
106        }
107        return providerList;
108    }
109
110    /**
111     * It is LoggerFactory's responsibility to track version changes and manage
112     * the compatibility list.
113     * <p>
114     * <p>
115     * It is assumed that all versions in the 1.6 are mutually compatible.
116     */
117    static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.8", "1.7" };
118
119    // private constructor prevents instantiation
120    private LoggerFactory() {
121    }
122
123    /**
124     * Force LoggerFactory to consider itself uninitialized.
125     * <p>
126     * <p>
127     * This method is intended to be called by classes (in the same package) for
128     * testing purposes. This method is internal. It can be modified, renamed or
129     * removed at any time without notice.
130     * <p>
131     * <p>
132     * You are strongly discouraged from calling this method in production code.
133     */
134    static void reset() {
135        INITIALIZATION_STATE = UNINITIALIZED;
136    }
137
138    private final static void performInitialization() {
139        bind();
140        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
141            versionSanityCheck();
142        }
143    }
144
145    private final static void bind() {
146        try {
147            List<SLF4JServiceProvider> providersList = findServiceProviders();
148            reportMultipleBindingAmbiguity(providersList);
149            if (providersList != null && !providersList.isEmpty()) {
150                PROVIDER = providersList.get(0);
151                PROVIDER.initialize();
152                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
153                reportActualBinding(providersList);
154                fixSubstituteLoggers();
155                replayEvents();
156                // release all resources in SUBST_FACTORY
157                SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
158            } else {
159                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
160                Util.report("No SLF4J providers were found.");
161                Util.report("Defaulting to no-operation (NOP) logger implementation");
162                Util.report("See " + NO_PROVIDERS_URL + " for further details.");
163
164                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
165                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
166            }
167        } catch (Exception e) {
168            failedBinding(e);
169            throw new IllegalStateException("Unexpected initialization failure", e);
170        }
171    }
172
173    private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) {
174        if (staticLoggerBinderPathSet.isEmpty()) {
175            return;
176        }
177        Util.report("Class path contains SLF4J bindings targeting slf4j-api versions prior to 1.8.");
178        for (URL path : staticLoggerBinderPathSet) {
179            Util.report("Ignoring binding found at [" + path + "]");
180        }
181        Util.report("See " + IGNORED_BINDINGS_URL + " for an explanation.");
182   
183
184    }
185
186    // We need to use the name of the StaticLoggerBinder class, but we can't
187    // reference the class itself.
188    private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
189
190    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
191        // use Set instead of list in order to deal with bug #138
192        // LinkedHashSet appropriate here because it preserves insertion order
193        // during iteration
194        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
195        try {
196            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
197            Enumeration<URL> paths;
198            if (loggerFactoryClassLoader == null) {
199                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
200            } else {
201                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
202            }
203            while (paths.hasMoreElements()) {
204                URL path = paths.nextElement();
205                staticLoggerBinderPathSet.add(path);
206            }
207        } catch (IOException ioe) {
208            Util.report("Error getting resources from path", ioe);
209        }
210        return staticLoggerBinderPathSet;
211    }
212
213    private static void fixSubstituteLoggers() {
214        synchronized (SUBST_PROVIDER) {
215            SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization();
216            for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) {
217                Logger logger = getLogger(substLogger.getName());
218                substLogger.setDelegate(logger);
219            }
220        }
221
222    }
223
224    static void failedBinding(Throwable t) {
225        INITIALIZATION_STATE = FAILED_INITIALIZATION;
226        Util.report("Failed to instantiate SLF4J LoggerFactory", t);
227    }
228
229    private static void replayEvents() {
230        final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue();
231        final int queueSize = queue.size();
232        int count = 0;
233        final int maxDrain = 128;
234        List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain);
235        while (true) {
236            int numDrained = queue.drainTo(eventList, maxDrain);
237            if (numDrained == 0)
238                break;
239            for (SubstituteLoggingEvent event : eventList) {
240                replaySingleEvent(event);
241                if (count++ == 0)
242                    emitReplayOrSubstituionWarning(event, queueSize);
243            }
244            eventList.clear();
245        }
246    }
247
248    private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
249        if (event.getLogger().isDelegateEventAware()) {
250            emitReplayWarning(queueSize);
251        } else if (event.getLogger().isDelegateNOP()) {
252            // nothing to do
253        } else {
254            emitSubstitutionWarning();
255        }
256    }
257
258    private static void replaySingleEvent(SubstituteLoggingEvent event) {
259        if (event == null)
260            return;
261
262        SubstituteLogger substLogger = event.getLogger();
263        String loggerName = substLogger.getName();
264        if (substLogger.isDelegateNull()) {
265            throw new IllegalStateException("Delegate logger cannot be null at this state.");
266        }
267
268        if (substLogger.isDelegateNOP()) {
269            // nothing to do
270        } else if (substLogger.isDelegateEventAware()) {
271            substLogger.log(event);
272        } else {
273            Util.report(loggerName);
274        }
275    }
276
277    private static void emitSubstitutionWarning() {
278        Util.report("The following set of substitute loggers may have been accessed");
279        Util.report("during the initialization phase. Logging calls during this");
280        Util.report("phase were not honored. However, subsequent logging calls to these");
281        Util.report("loggers will work as normally expected.");
282        Util.report("See also " + SUBSTITUTE_LOGGER_URL);
283    }
284
285    private static void emitReplayWarning(int eventCount) {
286        Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
287        Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
288        Util.report("See also " + REPLAY_URL);
289    }
290
291    private final static void versionSanityCheck() {
292        try {
293            String requested = PROVIDER.getRequesteApiVersion();
294
295            boolean match = false;
296            for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
297                if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
298                    match = true;
299                }
300            }
301            if (!match) {
302                Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
303                                + Arrays.asList(API_COMPATIBILITY_LIST).toString());
304                Util.report("See " + VERSION_MISMATCH + " for further details.");
305            }
306        } catch (java.lang.NoSuchFieldError nsfe) {
307            // given our large user base and SLF4J's commitment to backward
308            // compatibility, we cannot cry here. Only for implementations
309            // which willingly declare a REQUESTED_API_VERSION field do we
310            // emit compatibility warnings.
311        } catch (Throwable e) {
312            // we should never reach here
313            Util.report("Unexpected problem occured during version sanity check", e);
314        }
315    }
316
317    private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) {
318        return providerList.size() > 1;
319    }
320
321    /**
322     * Prints a warning message on the console if multiple bindings were found
323     * on the class path. No reporting is done otherwise.
324     * 
325     */
326    private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) {
327        if (isAmbiguousProviderList(providerList)) {
328            Util.report("Class path contains multiple SLF4J providers.");
329            for (SLF4JServiceProvider provider : providerList) {
330                Util.report("Found provider [" + provider + "]");
331            }
332            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
333        }
334    }
335
336    private static void reportActualBinding(List<SLF4JServiceProvider> providerList) {
337        // binderPathSet can be null under Android
338        if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) {
339            Util.report("Actual provider is of type [" + providerList.get(0) + "]");
340        }
341    }
342
343    /**
344     * Return a logger named according to the name parameter using the
345     * statically bound {@link ILoggerFactory} instance.
346     * 
347     * @param name
348     *            The name of the logger.
349     * @return logger
350     */
351    public static Logger getLogger(String name) {
352        ILoggerFactory iLoggerFactory = getILoggerFactory();
353        return iLoggerFactory.getLogger(name);
354    }
355
356    /**
357     * Return a logger named corresponding to the class passed as parameter,
358     * using the statically bound {@link ILoggerFactory} instance.
359     * 
360     * <p>
361     * In case the the <code>clazz</code> parameter differs from the name of the
362     * caller as computed internally by SLF4J, a logger name mismatch warning
363     * will be printed but only if the
364     * <code>slf4j.detectLoggerNameMismatch</code> system property is set to
365     * true. By default, this property is not set and no warnings will be
366     * printed even in case of a logger name mismatch.
367     * 
368     * @param clazz
369     *            the returned logger will be named after clazz
370     * @return logger
371     * 
372     * 
373     * @see <a
374     *      href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected
375     *      logger name mismatch</a>
376     */
377    public static Logger getLogger(Class<?> clazz) {
378        Logger logger = getLogger(clazz.getName());
379        if (DETECT_LOGGER_NAME_MISMATCH) {
380            Class<?> autoComputedCallingClass = Util.getCallingClass();
381            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
382                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
383                                autoComputedCallingClass.getName()));
384                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
385            }
386        }
387        return logger;
388    }
389
390    private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
391        return !autoComputedCallingClass.isAssignableFrom(clazz);
392    }
393
394    /**
395     * Return the {@link ILoggerFactory} instance in use.
396     * <p>
397     * <p>
398     * ILoggerFactory instance is bound with this class at compile time.
399     * 
400     * @return the ILoggerFactory instance in use
401     */
402    public static ILoggerFactory getILoggerFactory() {
403        return getProvider().getLoggerFactory();
404    }
405
406    /**
407     * Return the {@link SLF4JServiceProvider} in use.
408
409     * @return provider in use
410     * @since 1.8.0
411     */
412    static SLF4JServiceProvider getProvider() {
413        if (INITIALIZATION_STATE == UNINITIALIZED) {
414            synchronized (LoggerFactory.class) {
415                if (INITIALIZATION_STATE == UNINITIALIZED) {
416                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
417                    performInitialization();
418                }
419            }
420        }
421        switch (INITIALIZATION_STATE) {
422        case SUCCESSFUL_INITIALIZATION:
423            return PROVIDER;
424        case NOP_FALLBACK_INITIALIZATION:
425            return NOP_FALLBACK_FACTORY;
426        case FAILED_INITIALIZATION:
427            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
428        case ONGOING_INITIALIZATION:
429            // support re-entrant behavior.
430            // See also http://jira.qos.ch/browse/SLF4J-97
431            return SUBST_PROVIDER;
432        }
433        throw new IllegalStateException("Unreachable code");
434    }
435}