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.lang.reflect.Constructor;
029import java.lang.reflect.InvocationTargetException;
030import java.net.URL;
031import java.security.AccessController;
032import java.security.PrivilegedAction;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Enumeration;
036import java.util.Iterator;
037import java.util.LinkedHashSet;
038import java.util.List;
039import java.util.ServiceConfigurationError;
040import java.util.ServiceLoader;
041import java.util.Set;
042import java.util.concurrent.LinkedBlockingQueue;
043
044import org.slf4j.event.SubstituteLoggingEvent;
045import org.slf4j.helpers.NOP_FallbackServiceProvider;
046import org.slf4j.helpers.Reporter;
047import org.slf4j.helpers.SubstituteLogger;
048import org.slf4j.helpers.SubstituteServiceProvider;
049import org.slf4j.helpers.Util;
050import org.slf4j.spi.MDCAdapter;
051import org.slf4j.spi.SLF4JServiceProvider;
052
053/**
054 * The <code>LoggerFactory</code> is a utility class producing Loggers for
055 * various logging APIs, e.g. logback, reload4j, log4j and JDK 1.4 logging.
056 * Other implementations such as {@link org.slf4j.helpers.NOPLogger NOPLogger} and
057 * SimpleLogger are also supported.
058 *
059 * <p><code>LoggerFactory</code>  is essentially a wrapper around an
060 * {@link ILoggerFactory} instance provided by a {@link SLF4JServiceProvider}.
061 *
062 * <p>
063 * Please note that all methods in <code>LoggerFactory</code> are static.
064 *
065 * @author Alexander Dorokhine
066 * @author Robert Elliot
067 * @author Ceki G&uuml;lc&uuml;
068 *
069 */
070public final class LoggerFactory {
071
072    static final String CODES_PREFIX = "https://www.slf4j.org/codes.html";
073
074    static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders";
075    static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings";
076
077    static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
078    static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
079    static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
080    static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
081    static final String REPLAY_URL = CODES_PREFIX + "#replay";
082
083    static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
084    static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also "
085                    + UNSUCCESSFUL_INIT_URL;
086
087    static final String CONNECTED_WITH_MSG = "Connected with provider of type [";
088
089    /**
090     * System property for explicitly setting the provider class. If set and the provider could be instantiated,
091     * then the service loading mechanism will be bypassed.
092     *
093     * @since 2.0.9
094     */
095    static final public String PROVIDER_PROPERTY_KEY = "slf4j.provider";
096
097    static final int UNINITIALIZED = 0;
098    static final int ONGOING_INITIALIZATION = 1;
099    static final int FAILED_INITIALIZATION = 2;
100    static final int SUCCESSFUL_INITIALIZATION = 3;
101    static final int NOP_FALLBACK_INITIALIZATION = 4;
102
103    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
104    static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider();
105    static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider();
106
107    // Support for detecting mismatched logger names.
108    static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
109    static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
110
111    static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
112
113    static volatile SLF4JServiceProvider PROVIDER;
114
115    // Package access for tests
116    static List<SLF4JServiceProvider> findServiceProviders() {
117        List<SLF4JServiceProvider> providerList = new ArrayList<>();
118
119        // retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
120        // loaded the present class to search for services
121        final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();
122
123        SLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory);
124        if(explicitProvider != null) {
125            providerList.add(explicitProvider);
126            return providerList;
127        }
128
129
130         ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);
131
132        Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
133        while (iterator.hasNext()) {
134            safelyInstantiate(providerList, iterator);
135        }
136        return providerList;
137    }
138
139    private static ServiceLoader<SLF4JServiceProvider> getServiceLoader(final ClassLoader classLoaderOfLoggerFactory) {
140        ServiceLoader<SLF4JServiceProvider> serviceLoader;
141        SecurityManager securityManager = System.getSecurityManager();
142        if(securityManager == null) {
143            serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
144        } else {
145            final PrivilegedAction<ServiceLoader<SLF4JServiceProvider>> action = () -> ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
146            serviceLoader = AccessController.doPrivileged(action);
147        }
148        return serviceLoader;
149    }
150
151    private static void safelyInstantiate(List<SLF4JServiceProvider> providerList, Iterator<SLF4JServiceProvider> iterator) {
152        try {
153            SLF4JServiceProvider provider = iterator.next();
154            providerList.add(provider);
155        } catch (ServiceConfigurationError e) {
156            Reporter.error("A service provider failed to instantiate:\n" + e.getMessage());
157        }
158    }
159
160    /**
161     * It is LoggerFactory's responsibility to track version changes and manage
162     * the compatibility list.
163     * <p>
164     */
165    static private final String[] API_COMPATIBILITY_LIST = new String[] { "2.0" };
166
167    // private constructor prevents instantiation
168    private LoggerFactory() {
169    }
170
171    /**
172     * Force LoggerFactory to consider itself uninitialized.
173     * <p>
174     * <p>
175     * This method is intended to be called by classes (in the same package) for
176     * testing purposes. This method is internal. It can be modified, renamed or
177     * removed at any time without notice.
178     * <p>
179     * <p>
180     * You are strongly discouraged from calling this method in production code.
181     */
182    static void reset() {
183        INITIALIZATION_STATE = UNINITIALIZED;
184    }
185
186    private final static void performInitialization() {
187        bind();
188        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
189            versionSanityCheck();
190        }
191    }
192
193    private final static void bind() {
194        try {
195            List<SLF4JServiceProvider> providersList = findServiceProviders();
196            reportMultipleBindingAmbiguity(providersList);
197            if (providersList != null && !providersList.isEmpty()) {
198                PROVIDER = providersList.get(0);
199                earlyBindMDCAdapter();
200                // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
201                PROVIDER.initialize();
202                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
203                reportActualBinding(providersList);
204            } else {
205                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
206                Reporter.warn("No SLF4J providers were found.");
207                Reporter.warn("Defaulting to no-operation (NOP) logger implementation");
208                Reporter.warn("See " + NO_PROVIDERS_URL + " for further details.");
209
210                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
211                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
212            }
213            postBindCleanUp();
214        } catch (Exception e) {
215            failedBinding(e);
216            throw new IllegalStateException("Unexpected initialization failure", e);
217        }
218    }
219
220    /**
221     * The value of PROVIDER.getMDCAdapter() can be null while PROVIDER has not yet initialized.
222     *
223     * However, SLF4JServiceProvider implementations are expected to initialize their internal
224     * MDCAdapter field in their constructor or on field declaration.
225     */
226    private static void earlyBindMDCAdapter() {
227        MDCAdapter mdcAdapter = PROVIDER.getMDCAdapter();
228        if(mdcAdapter != null) {
229            MDC.setMDCAdapter(mdcAdapter);
230        }
231    }
232
233    static SLF4JServiceProvider loadExplicitlySpecified(ClassLoader classLoader) {
234        String explicitlySpecified = System.getProperty(PROVIDER_PROPERTY_KEY);
235        if (null == explicitlySpecified || explicitlySpecified.isEmpty()) {
236            return null;
237        }
238        try {
239            String message = String.format("Attempting to load provider \"%s\" specified via \"%s\" system property", explicitlySpecified, PROVIDER_PROPERTY_KEY);
240            Reporter.info(message);
241            Class<?> clazz = classLoader.loadClass(explicitlySpecified);
242            Constructor<?> constructor = clazz.getConstructor();
243            Object provider = constructor.newInstance();
244            return (SLF4JServiceProvider) provider;
245        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
246            String message = String.format("Failed to instantiate the specified SLF4JServiceProvider (%s)", explicitlySpecified);
247            Reporter.error(message, e);
248            return null;
249        } catch (ClassCastException e) {
250            String message = String.format("Specified SLF4JServiceProvider (%s) does not implement SLF4JServiceProvider interface", explicitlySpecified);
251            Reporter.error(message, e);
252            return null;
253        }
254    }
255
256    private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) {
257        if (staticLoggerBinderPathSet.isEmpty()) {
258            return;
259        }
260        Reporter.warn("Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier.");
261
262        for (URL path : staticLoggerBinderPathSet) {
263            Reporter.warn("Ignoring binding found at [" + path + "]");
264        }
265        Reporter.warn("See " + IGNORED_BINDINGS_URL + " for an explanation.");
266
267    }
268
269    // We need to use the name of the StaticLoggerBinder class, but we can't
270    // reference the class itself.
271    private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
272
273    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
274        // use Set instead of list in order to deal with bug #138
275        // LinkedHashSet appropriate here because it preserves insertion order
276        // during iteration
277        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>();
278        try {
279            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
280            Enumeration<URL> paths;
281            if (loggerFactoryClassLoader == null) {
282                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
283            } else {
284                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
285            }
286            while (paths.hasMoreElements()) {
287                URL path = paths.nextElement();
288                staticLoggerBinderPathSet.add(path);
289            }
290        } catch (IOException ioe) {
291            Reporter.error("Error getting resources from path", ioe);
292        }
293        return staticLoggerBinderPathSet;
294    }
295
296    private static void postBindCleanUp() {
297        fixSubstituteLoggers();
298        replayEvents();
299        // release all resources in SUBST_FACTORY
300        SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
301    }
302
303    private static void fixSubstituteLoggers() {
304        synchronized (SUBST_PROVIDER) {
305            SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization();
306            for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) {
307                Logger logger = getLogger(substLogger.getName());
308                substLogger.setDelegate(logger);
309            }
310        }
311
312    }
313
314    static void failedBinding(Throwable t) {
315        INITIALIZATION_STATE = FAILED_INITIALIZATION;
316        Reporter.error("Failed to instantiate SLF4J LoggerFactory", t);
317    }
318
319    private static void replayEvents() {
320        final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue();
321        final int queueSize = queue.size();
322        int count = 0;
323        final int maxDrain = 128;
324        List<SubstituteLoggingEvent> eventList = new ArrayList<>(maxDrain);
325        while (true) {
326            int numDrained = queue.drainTo(eventList, maxDrain);
327            if (numDrained == 0)
328                break;
329            for (SubstituteLoggingEvent event : eventList) {
330                replaySingleEvent(event);
331                if (count++ == 0)
332                    emitReplayOrSubstituionWarning(event, queueSize);
333            }
334            eventList.clear();
335        }
336    }
337
338    private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
339        if (event.getLogger().isDelegateEventAware()) {
340            emitReplayWarning(queueSize);
341        } else if (event.getLogger().isDelegateNOP()) {
342            // nothing to do
343        } else {
344            emitSubstitutionWarning();
345        }
346    }
347
348    private static void replaySingleEvent(SubstituteLoggingEvent event) {
349        if (event == null)
350            return;
351
352        SubstituteLogger substLogger = event.getLogger();
353        String loggerName = substLogger.getName();
354        if (substLogger.isDelegateNull()) {
355            throw new IllegalStateException("Delegate logger cannot be null at this state.");
356        }
357
358        if (substLogger.isDelegateNOP()) {
359            // nothing to do
360        } else if (substLogger.isDelegateEventAware()) {
361            if(substLogger.isEnabledForLevel(event.getLevel())) {
362                substLogger.log(event);
363            }
364        } else {
365            Reporter.warn(loggerName);
366        }
367    }
368
369    private static void emitSubstitutionWarning() {
370        Reporter.warn("The following set of substitute loggers may have been accessed");
371        Reporter.warn("during the initialization phase. Logging calls during this");
372        Reporter.warn("phase were not honored. However, subsequent logging calls to these");
373        Reporter.warn("loggers will work as normally expected.");
374        Reporter.warn("See also " + SUBSTITUTE_LOGGER_URL);
375    }
376
377    private static void emitReplayWarning(int eventCount) {
378        Reporter.warn("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
379        Reporter.warn("now being replayed. These are subject to the filtering rules of the underlying logging system.");
380        Reporter.warn("See also " + REPLAY_URL);
381    }
382
383    private final static void versionSanityCheck() {
384        try {
385            String requested = PROVIDER.getRequestedApiVersion();
386
387            boolean match = false;
388            for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
389                if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
390                    match = true;
391                }
392            }
393            if (!match) {
394                Reporter.warn("The requested version " + requested + " by your slf4j provider is not compatible with "
395                                + Arrays.asList(API_COMPATIBILITY_LIST).toString());
396                Reporter.warn("See " + VERSION_MISMATCH + " for further details.");
397            }
398        } catch (Throwable e) {
399            // we should never reach here
400            Reporter.error("Unexpected problem occurred during version sanity check", e);
401        }
402    }
403
404    private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) {
405        return providerList.size() > 1;
406    }
407
408    /**
409     * Prints a warning message on the console if multiple bindings were found
410     * on the class path. No reporting is done otherwise.
411     *
412     */
413    private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) {
414        if (isAmbiguousProviderList(providerList)) {
415            Reporter.warn("Class path contains multiple SLF4J providers.");
416            for (SLF4JServiceProvider provider : providerList) {
417                Reporter.warn("Found provider [" + provider + "]");
418            }
419            Reporter.warn("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
420        }
421    }
422
423    private static void reportActualBinding(List<SLF4JServiceProvider> providerList) {
424        // impossible since a provider has been found
425        if (providerList.isEmpty()) {
426            throw new IllegalStateException("No providers were found which is impossible after successful initialization.");
427        }
428
429        if (isAmbiguousProviderList(providerList)) {
430            Reporter.info("Actual provider is of type [" + providerList.get(0) + "]");
431        } else {
432            SLF4JServiceProvider provider = providerList.get(0);
433            Reporter.debug(CONNECTED_WITH_MSG + provider.getClass().getName() + "]");
434        }
435    }
436
437    /**
438     * Return a logger named according to the name parameter using the
439     * statically bound {@link ILoggerFactory} instance.
440     *
441     * @param name
442     *            The name of the logger.
443     * @return logger
444     */
445    public static Logger getLogger(String name) {
446        ILoggerFactory iLoggerFactory = getILoggerFactory();
447        return iLoggerFactory.getLogger(name);
448    }
449
450    /**
451     * Return a logger named corresponding to the class passed as parameter,
452     * using the statically bound {@link ILoggerFactory} instance.
453     *
454     * <p>
455     * In case the <code>clazz</code> parameter differs from the name of the
456     * caller as computed internally by SLF4J, a logger name mismatch warning
457     * will be printed but only if the
458     * <code>slf4j.detectLoggerNameMismatch</code> system property is set to
459     * true. By default, this property is not set and no warnings will be
460     * printed even in case of a logger name mismatch.
461     *
462     * @param clazz
463     *            the returned logger will be named after clazz
464     * @return logger
465     *
466     *
467     * @see <a
468     *      href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected
469     *      logger name mismatch</a>
470     */
471    public static Logger getLogger(Class<?> clazz) {
472        Logger logger = getLogger(clazz.getName());
473        if (DETECT_LOGGER_NAME_MISMATCH) {
474            Class<?> autoComputedCallingClass = Util.getCallingClass();
475            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
476                Reporter.warn(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
477                                autoComputedCallingClass.getName()));
478                Reporter.warn("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
479            }
480        }
481        return logger;
482    }
483
484    private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
485        return !autoComputedCallingClass.isAssignableFrom(clazz);
486    }
487
488    /**
489     * Return the {@link ILoggerFactory} instance in use.
490     * <p>
491     * <p>
492     * ILoggerFactory instance is bound with this class at compile time.
493     *
494     * @return the ILoggerFactory instance in use
495     */
496    public static ILoggerFactory getILoggerFactory() {
497        return getProvider().getLoggerFactory();
498    }
499
500    /**
501     * Return the {@link SLF4JServiceProvider} in use.
502
503     * @return provider in use
504     * @since 1.8.0
505     */
506    static SLF4JServiceProvider getProvider() {
507        if (INITIALIZATION_STATE == UNINITIALIZED) {
508            synchronized (LoggerFactory.class) {
509                if (INITIALIZATION_STATE == UNINITIALIZED) {
510                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
511                    performInitialization();
512                }
513            }
514        }
515        switch (INITIALIZATION_STATE) {
516        case SUCCESSFUL_INITIALIZATION:
517            return PROVIDER;
518        case NOP_FALLBACK_INITIALIZATION:
519            return NOP_FALLBACK_SERVICE_PROVIDER;
520        case FAILED_INITIALIZATION:
521            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
522        case ONGOING_INITIALIZATION:
523            // support re-entrant behavior.
524            // See also http://jira.qos.ch/browse/SLF4J-97
525            return SUBST_PROVIDER;
526        }
527        throw new IllegalStateException("Unreachable code");
528    }
529}