Source code interpretation of SLF4J binding log implementation principle

1, Guide reading

When we use the log4j framework, we often use the slf4j API. When running, you often encounter the following error prompts:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/slf4j/slf4j-simple/1.7.26/slf4j-simple-1.7.26.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
That is to say   There are multiple log implementations under classpath, and slf4j API conflicts during binding. So which specific implementation will slf4j API bind to?
 
The official documents are as follows:

 

That is, when there are multiple log implementations, SLF4J randomly selects one of them at compile time. So, how is it chosen at random? Let's analyze the source code.

Please respect the author's work achievements, and indicate the link of the original text for Reprint:   https://www.cnblogs.com/waterystone/p/11329645.html
 

2, Source code analysis

2.1 LoggerFactory.getLogger(this.getClass())

We get the Logger object through LoggerFactory.getLogger(this.getClass()). The code is as follows:
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;
}

  

2.2 LoggerFactory.bind()

Getlogger() - > getiloggerfactory() - > performaninitialization() - > bind(), which is a function implemented by binding logs, its source code is as follows:
private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        // skip check under android, see also
        // http://jira.qos.ch/browse/SLF4J-328
        if (!isAndroid()) {
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); //How many logs are found under classpath
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); //If there are multiple log implementations, print them out
        }
        // the next line does the binding.  Under classpath, each log implementation jar will have an org.slf4j.impl.StaticLoggerBinder class. Here, one of them will be loaded randomly.
        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstituteLoggers();
        replayEvents();
        // release all resources in SUBST_FACTORY
        SUBST_FACTORY.clear();
    } catch (NoClassDefFoundError ncde) { //If no org.slf4j.impl.StaticLoggerBinder is found under classpath
        String msg = ncde.getMessage();
        if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
            INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
            Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
        } else {
            failedBinding(ncde);
            throw ncde;
        }
    } catch (java.lang.NoSuchMethodError nsme) {
        String msg = nsme.getMessage();
        if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
            INITIALIZATION_STATE = FAILED_INITIALIZATION;
            Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
            Util.report("Your binding is version 1.5.5 or earlier.");
            Util.report("Upgrade your binding to version 1.6.x.");
        }
        throw nsme;
    } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    }
}

  

2.3 LoggerFactory.findPossibleStaticLoggerBinderPathSet()

Next, let's look at how findPossibleStaticLoggerBinderPathSet() finds the source code of how many log implementations under classpath:
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    // use Set instead of list in order to deal with bug #138
    // LinkedHashSet appropriate here because it preserves insertion order
    // during iteration
    Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
    try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        Enumeration<URL> paths;
        if (loggerFactoryClassLoader == null) { //Use ClassLoader to find how many org.slf4j.impl.StaticLoggerBinder classes are under classpath
            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;
}

  

We can see from the source code,   findPossibleStaticLoggerBinderPathSet() will use ClassLoader to find how many org.slf4j.impl.StaticLoggerBinder classes are under the current classpath. The URL of each path can be located to its specific jar package location.   Each log implementation jar package will have an org.slf4j.impl.StaticLoggerBinder class. Conversely, how many StaticLoggerBinder classes under the classpath will have corresponding jar packages, that is, how many log implementations. So findPossibleStaticLoggerBinderPathSet() can find how many log implementations there are by scanning the org.slf4j.impl.StaticLoggerBinder class under the classpath.
 
next,   Let's see if each log implementation jar package will have an org.slf4j.impl.StaticLoggerBinder class? The answer is YES.
 

3, Specific log implementation

3.1 slf4j-simple

 

3.2 slf4j-nop

 

3.3 logback-classic

 

3.4 log4j-slf4j-impl

 

4, Summary

  • Each log implementation jar will have the implementation of org.slf4j.impl.StaticLoggerBinder class;
  • SLF4J will scan how many org.slf4j.impl.StaticLoggerBinder classes are in the current classpath through ClassLoad, and find how many log implementations are there (in this way, we only need to add the jar package implemented by the log into the project, which can be loaded automatically at compile time, and the business code does not need to be explicitly dependent to realize decoupling);
  • If there is more than one org.slf4j.impl.StaticLoggerBinder class, SLF4J will call StaticLoggerBinder.getSingleton() in LoggerFactory.bind() to randomly load a StaticLoggerBinder of the log implementation jar.
  • An interesting phenomenon is found: when running on the local idea, it is the first log implementation declared in pom.xml to be loaded; however, if it is started through Java jar after being packaged, the log implementation loaded is indeed random (it is a log implementation loaded randomly when compiling and packaging, so once compiled and packaged, the log implementation loaded will be fixed). Therefore, we must determine the log implementation by eliminating the dependency when we use the log, and do not introduce the difficult and unnecessary pits due to the uncertainty of the log implementation.

Tags: Programming log4j Maven Java Apache

Posted on Mon, 11 May 2020 04:50:55 -0400 by fahrvergnuugen