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