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.NOP_FallbackServiceProvider;
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.helpers.NOPLogger NOPLogger} and
050 * 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 = "https://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 NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider();
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<>();
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[] { "2.0" };
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                // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
152                PROVIDER.initialize();
153                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
154                reportActualBinding(providersList);
155            } else {
156                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
157                Util.report("No SLF4J providers were found.");
158                Util.report("Defaulting to no-operation (NOP) logger implementation");
159                Util.report("See " + NO_PROVIDERS_URL + " for further details.");
160
161                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
162                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
163            }
164            postBindCleanUp();
165        } catch (Exception e) {
166            failedBinding(e);
167            throw new IllegalStateException("Unexpected initialization failure", e);
168        }
169    }
170
171    private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) {
172        if (staticLoggerBinderPathSet.isEmpty()) {
173            return;
174        }
175        Util.report("Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier.");
176
177        for (URL path : staticLoggerBinderPathSet) {
178            Util.report("Ignoring binding found at [" + path + "]");
179        }
180        Util.report("See " + IGNORED_BINDINGS_URL + " for an explanation.");
181
182    }
183
184    // We need to use the name of the StaticLoggerBinder class, but we can't
185    // reference the class itself.
186    private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
187
188    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
189        // use Set instead of list in order to deal with bug #138
190        // LinkedHashSet appropriate here because it preserves insertion order
191        // during iteration
192        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>();
193        try {
194            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
195            Enumeration<URL> paths;
196            if (loggerFactoryClassLoader == null) {
197                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
198            } else {
199                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
200            }
201            while (paths.hasMoreElements()) {
202                URL path = paths.nextElement();
203                staticLoggerBinderPathSet.add(path);
204            }
205        } catch (IOException ioe) {
206            Util.report("Error getting resources from path", ioe);
207        }
208        return staticLoggerBinderPathSet;
209    }
210
211    private static void postBindCleanUp() {
212        fixSubstituteLoggers();
213        replayEvents();
214        // release all resources in SUBST_FACTORY
215        SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
216    }
217
218    private static void fixSubstituteLoggers() {
219        synchronized (SUBST_PROVIDER) {
220            SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization();
221            for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) {
222                Logger logger = getLogger(substLogger.getName());
223                substLogger.setDelegate(logger);
224            }
225        }
226
227    }
228
229    static void failedBinding(Throwable t) {
230        INITIALIZATION_STATE = FAILED_INITIALIZATION;
231        Util.report("Failed to instantiate SLF4J LoggerFactory", t);
232    }
233
234    private static void replayEvents() {
235        final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue();
236        final int queueSize = queue.size();
237        int count = 0;
238        final int maxDrain = 128;
239        List<SubstituteLoggingEvent> eventList = new ArrayList<>(maxDrain);
240        while (true) {
241            int numDrained = queue.drainTo(eventList, maxDrain);
242            if (numDrained == 0)
243                break;
244            for (SubstituteLoggingEvent event : eventList) {
245                replaySingleEvent(event);
246                if (count++ == 0)
247                    emitReplayOrSubstituionWarning(event, queueSize);
248            }
249            eventList.clear();
250        }
251    }
252
253    private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
254        if (event.getLogger().isDelegateEventAware()) {
255            emitReplayWarning(queueSize);
256        } else if (event.getLogger().isDelegateNOP()) {
257            // nothing to do
258        } else {
259            emitSubstitutionWarning();
260        }
261    }
262
263    private static void replaySingleEvent(SubstituteLoggingEvent event) {
264        if (event == null)
265            return;
266
267        SubstituteLogger substLogger = event.getLogger();
268        String loggerName = substLogger.getName();
269        if (substLogger.isDelegateNull()) {
270            throw new IllegalStateException("Delegate logger cannot be null at this state.");
271        }
272
273        if (substLogger.isDelegateNOP()) {
274            // nothing to do
275        } else if (substLogger.isDelegateEventAware()) {
276            substLogger.log(event);
277        } else {
278            Util.report(loggerName);
279        }
280    }
281
282    private static void emitSubstitutionWarning() {
283        Util.report("The following set of substitute loggers may have been accessed");
284        Util.report("during the initialization phase. Logging calls during this");
285        Util.report("phase were not honored. However, subsequent logging calls to these");
286        Util.report("loggers will work as normally expected.");
287        Util.report("See also " + SUBSTITUTE_LOGGER_URL);
288    }
289
290    private static void emitReplayWarning(int eventCount) {
291        Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
292        Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
293        Util.report("See also " + REPLAY_URL);
294    }
295
296    private final static void versionSanityCheck() {
297        try {
298            String requested = PROVIDER.getRequestedApiVersion();
299
300            boolean match = false;
301            for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
302                if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
303                    match = true;
304                }
305            }
306            if (!match) {
307                Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
308                                + Arrays.asList(API_COMPATIBILITY_LIST).toString());
309                Util.report("See " + VERSION_MISMATCH + " for further details.");
310            }
311        } catch (java.lang.NoSuchFieldError nsfe) {
312            // given our large user base and SLF4J's commitment to backward
313            // compatibility, we cannot cry here. Only for implementations
314            // which willingly declare a REQUESTED_API_VERSION field do we
315            // emit compatibility warnings.
316        } catch (Throwable e) {
317            // we should never reach here
318            Util.report("Unexpected problem occured during version sanity check", e);
319        }
320    }
321
322    private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) {
323        return providerList.size() > 1;
324    }
325
326    /**
327     * Prints a warning message on the console if multiple bindings were found
328     * on the class path. No reporting is done otherwise.
329     * 
330     */
331    private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) {
332        if (isAmbiguousProviderList(providerList)) {
333            Util.report("Class path contains multiple SLF4J providers.");
334            for (SLF4JServiceProvider provider : providerList) {
335                Util.report("Found provider [" + provider + "]");
336            }
337            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
338        }
339    }
340
341    private static void reportActualBinding(List<SLF4JServiceProvider> providerList) {
342        // binderPathSet can be null under Android
343        if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) {
344            Util.report("Actual provider is of type [" + providerList.get(0) + "]");
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 <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        return getProvider().getLoggerFactory();
409    }
410
411    /**
412     * Return the {@link SLF4JServiceProvider} in use.
413    
414     * @return provider in use
415     * @since 1.8.0
416     */
417    static SLF4JServiceProvider getProvider() {
418        if (INITIALIZATION_STATE == UNINITIALIZED) {
419            synchronized (LoggerFactory.class) {
420                if (INITIALIZATION_STATE == UNINITIALIZED) {
421                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
422                    performInitialization();
423                }
424            }
425        }
426        switch (INITIALIZATION_STATE) {
427        case SUCCESSFUL_INITIALIZATION:
428            return PROVIDER;
429        case NOP_FALLBACK_INITIALIZATION:
430            return NOP_FALLBACK_SERVICE_PROVIDER;
431        case FAILED_INITIALIZATION:
432            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
433        case ONGOING_INITIALIZATION:
434            // support re-entrant behavior.
435            // See also http://jira.qos.ch/browse/SLF4J-97
436            return SUBST_PROVIDER;
437        }
438        throw new IllegalStateException("Unreachable code");
439    }
440}