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