org.slf4j.LoggerFactory Maven / Gradle / Ivy
/**
* Copyright (c) 2004-2011 QOS.ch
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j;
import java.io.IOException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.event.SubstituteLoggingEvent;
import org.slf4j.helpers.NOP_FallbackServiceProvider;
import org.slf4j.helpers.SubstituteLogger;
import org.slf4j.helpers.SubstituteServiceProvider;
import org.slf4j.helpers.Util;
import org.slf4j.spi.SLF4JServiceProvider;
import javax.accessibility.AccessibleComponent;
/**
* The LoggerFactory
is a utility class producing Loggers for
* various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
* Other implementations such as {@link org.slf4j.helpers.NOPLogger NOPLogger} and
* SimpleLogger are also supported.
*
* LoggerFactory
is essentially a wrapper around an
* {@link ILoggerFactory} instance bound with LoggerFactory
at
* compile time.
*
*
* Please note that all methods in LoggerFactory
are static.
*
* @author Alexander Dorokhine
* @author Robert Elliot
* @author Ceki Gülcü
*
*/
public final class LoggerFactory {
static final String CODES_PREFIX = "https://www.slf4j.org/codes.html";
static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders";
static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings";
static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
static final String REPLAY_URL = CODES_PREFIX + "#replay";
static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also "
+ UNSUCCESSFUL_INIT_URL;
static final int UNINITIALIZED = 0;
static final int ONGOING_INITIALIZATION = 1;
static final int FAILED_INITIALIZATION = 2;
static final int SUCCESSFUL_INITIALIZATION = 3;
static final int NOP_FALLBACK_INITIALIZATION = 4;
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider();
static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider();
// Support for detecting mismatched logger names.
static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
static volatile SLF4JServiceProvider PROVIDER;
// Package access for tests
static List findServiceProviders() {
// retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
// loaded the present class to search for services
final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();
ServiceLoader serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);
List providerList = new ArrayList<>();
Iterator iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
safelyInstantiate(providerList, iterator);
}
return providerList;
}
private static ServiceLoader getServiceLoader(final ClassLoader classLoaderOfLoggerFactory) {
ServiceLoader serviceLoader;
SecurityManager securityManager = System.getSecurityManager();
if(securityManager == null) {
serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
} else {
final PrivilegedAction> action = () -> ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
serviceLoader = AccessController.doPrivileged(action);
}
return serviceLoader;
}
private static void safelyInstantiate(List providerList, Iterator iterator) {
try {
SLF4JServiceProvider provider = iterator.next();
providerList.add(provider);
} catch (ServiceConfigurationError e) {
Util.report("A SLF4J service provider failed to instantiate:\n" + e.getMessage());
}
}
/**
* It is LoggerFactory's responsibility to track version changes and manage
* the compatibility list.
*
*
* It is assumed that all versions in the 1.6 are mutually compatible.
*/
static private final String[] API_COMPATIBILITY_LIST = new String[] { "2.0" };
// private constructor prevents instantiation
private LoggerFactory() {
}
/**
* Force LoggerFactory to consider itself uninitialized.
*
*
* This method is intended to be called by classes (in the same package) for
* testing purposes. This method is internal. It can be modified, renamed or
* removed at any time without notice.
*
*
* You are strongly discouraged from calling this method in production code.
*/
static void reset() {
INITIALIZATION_STATE = UNINITIALIZED;
}
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
private final static void bind() {
try {
List providersList = findServiceProviders();
reportMultipleBindingAmbiguity(providersList);
if (providersList != null && !providersList.isEmpty()) {
PROVIDER = providersList.get(0);
// SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
PROVIDER.initialize();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(providersList);
} else {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("No SLF4J providers were found.");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_PROVIDERS_URL + " for further details.");
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
}
postBindCleanUp();
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
private static void reportIgnoredStaticLoggerBinders(Set staticLoggerBinderPathSet) {
if (staticLoggerBinderPathSet.isEmpty()) {
return;
}
Util.report("Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier.");
for (URL path : staticLoggerBinderPathSet) {
Util.report("Ignoring binding found at [" + path + "]");
}
Util.report("See " + IGNORED_BINDINGS_URL + " for an explanation.");
}
// We need to use the name of the StaticLoggerBinder class, but we can't
// reference the class itself.
private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet<>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
private static void postBindCleanUp() {
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
}
private static void fixSubstituteLoggers() {
synchronized (SUBST_PROVIDER) {
SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization();
for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) {
Logger logger = getLogger(substLogger.getName());
substLogger.setDelegate(logger);
}
}
}
static void failedBinding(Throwable t) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("Failed to instantiate SLF4J LoggerFactory", t);
}
private static void replayEvents() {
final LinkedBlockingQueue queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue();
final int queueSize = queue.size();
int count = 0;
final int maxDrain = 128;
List eventList = new ArrayList<>(maxDrain);
while (true) {
int numDrained = queue.drainTo(eventList, maxDrain);
if (numDrained == 0)
break;
for (SubstituteLoggingEvent event : eventList) {
replaySingleEvent(event);
if (count++ == 0)
emitReplayOrSubstituionWarning(event, queueSize);
}
eventList.clear();
}
}
private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
if (event.getLogger().isDelegateEventAware()) {
emitReplayWarning(queueSize);
} else if (event.getLogger().isDelegateNOP()) {
// nothing to do
} else {
emitSubstitutionWarning();
}
}
private static void replaySingleEvent(SubstituteLoggingEvent event) {
if (event == null)
return;
SubstituteLogger substLogger = event.getLogger();
String loggerName = substLogger.getName();
if (substLogger.isDelegateNull()) {
throw new IllegalStateException("Delegate logger cannot be null at this state.");
}
if (substLogger.isDelegateNOP()) {
// nothing to do
} else if (substLogger.isDelegateEventAware()) {
if(substLogger.isEnabledForLevel(event.getLevel())) {
substLogger.log(event);
}
} else {
Util.report(loggerName);
}
}
private static void emitSubstitutionWarning() {
Util.report("The following set of substitute loggers may have been accessed");
Util.report("during the initialization phase. Logging calls during this");
Util.report("phase were not honored. However, subsequent logging calls to these");
Util.report("loggers will work as normally expected.");
Util.report("See also " + SUBSTITUTE_LOGGER_URL);
}
private static void emitReplayWarning(int eventCount) {
Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
Util.report("See also " + REPLAY_URL);
}
private final static void versionSanityCheck() {
try {
String requested = PROVIDER.getRequestedApiVersion();
boolean match = false;
for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
match = true;
}
}
if (!match) {
Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
+ Arrays.asList(API_COMPATIBILITY_LIST).toString());
Util.report("See " + VERSION_MISMATCH + " for further details.");
}
} catch (java.lang.NoSuchFieldError nsfe) {
// given our large user base and SLF4J's commitment to backward
// compatibility, we cannot cry here. Only for implementations
// which willingly declare a REQUESTED_API_VERSION field do we
// emit compatibility warnings.
} catch (Throwable e) {
// we should never reach here
Util.report("Unexpected problem occured during version sanity check", e);
}
}
private static boolean isAmbiguousProviderList(List providerList) {
return providerList.size() > 1;
}
/**
* Prints a warning message on the console if multiple bindings were found
* on the class path. No reporting is done otherwise.
*
*/
private static void reportMultipleBindingAmbiguity(List providerList) {
if (isAmbiguousProviderList(providerList)) {
Util.report("Class path contains multiple SLF4J providers.");
for (SLF4JServiceProvider provider : providerList) {
Util.report("Found provider [" + provider + "]");
}
Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
}
}
private static void reportActualBinding(List providerList) {
// binderPathSet can be null under Android
if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) {
Util.report("Actual provider is of type [" + providerList.get(0) + "]");
}
}
/**
* Return a logger named according to the name parameter using the
* statically bound {@link ILoggerFactory} instance.
*
* @param name
* The name of the logger.
* @return logger
*/
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
/**
* Return a logger named corresponding to the class passed as parameter,
* using the statically bound {@link ILoggerFactory} instance.
*
*
* In case the clazz
parameter differs from the name of the
* caller as computed internally by SLF4J, a logger name mismatch warning
* will be printed but only if the
* slf4j.detectLoggerNameMismatch
system property is set to
* true. By default, this property is not set and no warnings will be
* printed even in case of a logger name mismatch.
*
* @param clazz
* the returned logger will be named after clazz
* @return logger
*
*
* @see Detected
* logger name mismatch
*/
public static Logger getLogger(Class> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
private static boolean nonMatchingClasses(Class> clazz, Class> autoComputedCallingClass) {
return !autoComputedCallingClass.isAssignableFrom(clazz);
}
/**
* Return the {@link ILoggerFactory} instance in use.
*
*
* ILoggerFactory instance is bound with this class at compile time.
*
* @return the ILoggerFactory instance in use
*/
public static ILoggerFactory getILoggerFactory() {
return getProvider().getLoggerFactory();
}
/**
* Return the {@link SLF4JServiceProvider} in use.
* @return provider in use
* @since 1.8.0
*/
static SLF4JServiceProvider getProvider() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return PROVIDER;
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_SERVICE_PROVIDER;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_PROVIDER;
}
throw new IllegalStateException("Unreachable code");
}
}