How is slf4j bound to a specific log framework

Keywords: Programming Lombok Java log4j github

SLF4J is a Simple Logging Facade for Java, which is a facade (appearance) interface or an abstraction of various logging frameworks, such as java.util.logging, logback, log4j, etc. with this product, we can not care about the specific implementation, that is to say, we can switch the logging framework at any time.

The latest version of slf4j 1.8.0-beta2 is used here

Easy to use

Sample code https://github.com/minorpoet/logging-slf4j

  1. Add dependency

 

dependencies {
    compile group: 'org.slf4j', name: 'slf4j-api', version: '1.8.0-beta2'
}
  1. Use

 

package pri.holysu.logging.sl4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {

    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(HelloWorld.class);
        logger.info("hello world");
    }
}

  1. Function

     

    slf4j-nobind

It is found that the error is reported, indicating that we have not found the implementation of slf4j

How? Plus one

Add a logback to build.gradle dependency

 

dependencies {
    compile group: 'org.slf4j', name: 'slf4j-api', version: '1.8.0-beta2'
    compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.3.0-alpha4'
 }

Then run it again and find it's ok

 

slf4j-logback

However, I haven't made any settings. How can I choose the log framework of logback?

Let's see how to use it. Logger logger = LoggerFactory.getLogger(HelloWorld.class);
The static method of this log factory org.slf4j.LoggerFactory.getLogger

 

    public static Logger getLogger(Class<?> clazz) {
        // Get Logger by class name 
        Logger logger = getLogger(clazz.getName());
       // If "check log name matching" is set on in the configuration file, an error will be reported when the matching fails
        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;
    }

Take a look at getLogger

 

 public static Logger getLogger(String name) {
       //  Get log factory
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

 public static ILoggerFactory getILoggerFactory() {
        // Get the log factory from the log framework provider (Implementation)
        return getProvider().getLoggerFactory();
    }

Find here, getProvider. Literally, it should be the place we are looking for

 

  static SLF4JServiceProvider getProvider() {
       // When the status is uninitialized, this is the double checked lock mode
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    // Initializ ing with status set to
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    // Execute initialization logic
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return PROVIDER;
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        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");
    }

Specific initialization process:

 

   private final static void performInitialization() {
       // Binding log framework, this is the core
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            // After initialization, check the health of the log framework
            versionSanityCheck();
        }
    }

private final static void bind() {
        try {
             // Implementation of loading slf4j
            List<SLF4JServiceProvider> providersList = findServiceProviders();
            reportMultipleBindingAmbiguity(providersList);
            if (providersList != null && !providersList.isEmpty()) {
                // Get only the first implementation
                PROVIDER = providersList.get(0);
                PROVIDER.initialize();
                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                reportActualBinding(providersList);
                fixSubstituteLoggers();
                replayEvents();
                // release all resources in SUBST_FACTORY
                SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
            } else {
               // When the logback package was not introduced at the beginning, this error was reported
                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<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
            }
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

The specific logic of findServiceProviders is very clear, and the specified type is loaded through class loader

 

 private static List<SLF4JServiceProvider> findServiceProviders() {
       // Load the tool class through the ServiceLoader interface, and load the class of slf4jsserviceprovider type
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
    // Add to the list, and through PROVIDER = providersList.get(0) in bind() above, we can see that even if there are multiple implementation classes, only the first one loaded will be used
List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
        for (SLF4JServiceProvider provider : serviceLoader) {
            providerList.add(provider);
        }
        return providerList;
    }
// Load a class of type service through the classloader of the current thread
 public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

org.slf4j.LoggerFactory.getLogger avoids the tedious things like ILogger logger = new LoggerImp(); or bean declaration. As long as the implementation exists in the classpath, we only need one format when we need to record logs, regardless of the implementation differences of various log frameworks. This is the charm of the specification~

If you use lombok, it will be more concise, such as

 

package pri.holysu.logging.sl4j;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LombokSlf4j {

    public static void main(String[] args) {
        log.info("logger feteched from lombok");
    }
}

Among them, log is added automatically by lombok, isn't it convenient?

 


 

Posted by lovelf on Tue, 07 Jan 2020 04:02:05 -0800