Commons-logging+log4j Source Code Analysis

Time:2019-8-8

Clarify several concepts before analyzing

Log4J = Log For Java

SLF4J = Simple Logging Facade for Java

The first thing Facade thinks of is the Facade pattern in the design pattern. In fact, SLF4J is a “facade” Java log framework. It only provides an abstract and generic log API for the caller to write the log, while the log4J, logback and other frameworks are the real implementation of the log writing function and are opened after jdk1.4. The java. util. logging package that was first provided depends on the strategy set up in SLF4J, and the facade mode is indeed used as a whole.

Recommendations for the birth and relationship of these log frameworks: https://blog.csdn.net/qq_32625839/article/details/80893550

 

Apache’s commons-logging, like SLF4J, is an abstract log framework, which is more widely used. Here’s how to implement its internal facade pattern through several source code analysis.

Generally, before writing a log, you need to use the following method to get the log object

Log log = LogFactory.getLog(clz.getName());

Enter the getLog method
    public static Log getLog(String name) throws LogConfigurationException {
        return getFactory().getInstance(name);
    }

It seems to be to get the log factory object first, then the log object, and then the getFactory method.

// Get the class loader in context
ClassLoader contextClassLoader = getContextClassLoaderInternal();
// First try to get LogFactory from the cache
LogFactory factory = getCachedFactory(contextClassLoader); if (factory != null) {   return factory; }

// If there is a commons-logging. properties configuration file and the use_tccl configuration item is false, then thisClassLoader is used as the subsequent class loader, and thisClassLoader is the loader of the current class.
  Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
  ClassLoader baseClassLoader = contextClassLoader;
  if (props != null) {
    String useTCCLStr = props.getProperty(TCCL_KEY);
    if (useTCCLStr != null) {
      if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
        baseClassLoader = thisClassLoader;
      }
    }
  }

// Get the system configuration item of org. apache. commons. logging. LogFactory, and create a new factory object using the configured class if it exists
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
}

// Use SPI mechanism in Java to build new factory objects according to configuration class in META-INF/services/org.apache.commons.logging.LogFactory
if (factory == null) {
try {
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
     ...
     factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
     ...

// Create a new factory object based on the configuration attribute org. apache. commons. logging. LogFactory in commons-logging. properties

  if (factory == null) {
    String factoryClass = props.getProperty(FACTORY_PROPERTY);
    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);

// If none of the above succeeds in creating factory objects, create them directly using the default org. apache. commons. logging. impl. LogFactoryImpl class

factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);

// Finally put in the cache

cacheFactory(contextClassLoader, factory);

Normally, the default logFactory implementation class is LogFactoryImpl, and then go into its getInstance method.

 

public Log getInstance(String name) throws LogConfigurationException {
        Log instance = (Log) instances.get(name);
        if (instance == null) {
            instance = newInstance(name);
            instances.put(name, instance);
        }
        return instance;
    }

Enter new Instance

    protected Log newInstance(String name) throws LogConfigurationException {
        Log instance;
        try {
            if (logConstructor == null) {
                instance = discoverLogImplementation(name);
            }
            else {
                Object params[] = { name };
                instance = (Log) logConstructor.newInstance(params);
            }

            if (logMethod != null) {
                Object params[] = { this };
                logMethod.invoke(instance, params);
            }

            return instance;

Enter discoverLog Implementation

    private Log discoverLogImplementation(String logCategory)
        throws LogConfigurationException {
        initConfiguration();
        Log result = null;

// The findUserSpecifiedLogClassName method also reads some system configuration items, and if read, creates log objects based on the class name of the configuration.
        String specifiedLogClassName = findUserSpecifiedLogClassName();
        if (specifiedLogClassName != null) {
            result = createLogFromClass(specifiedLogClassName,logCategory,true);
            if (result == null) {
                StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");
                messageBuffer.append(specifiedLogClassName);
                messageBuffer.append("' cannot be found or is not useable.");
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
                throw new LogConfigurationException(messageBuffer.toString());
            }
            return result;
        }
     //If the above creation is unsuccessful, the log objects are created as class names according to the values of the classesToDiscover array, and returned until the creation is successful (not empty).
     for(int i=0; i<classesToDiscover.length && result == null; ++i) { result = createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException ("No suitable Log implementation"); } return result; }

Where the value of the classesToDiscover array is written to death

private static final String[] classesToDiscover = {
  ”org.apache.commons.logging.impl.Log4JLogger”,
  ”org.apache.commons.logging.impl.Jdk14Logger”,
  ”org.apache.commons.logging.impl.Jdk13LumberjackLogger”,
  ”org.apache.commons.logging.impl.SimpleLog”
}

So when we introduce commons-logging and Log4j jar packages into our project, we don’t really need to do any configuration, so we will use Log4JLogger as the actual log implementation class first.

There are two points to be noted in the whole process:

1. Log factory objects and log implementation class objects are loaded using classLoad of the current class or thread context before creating objects by reflection. If a plug-in is loaded using a custom classLoad, it may not be able to create log objects or use it when printing logs inside the plug-in. Log objects that are inconsistent with expectations

2. Dynamic lookup: Dynamic lookup of log factory implementation class and log implementation class is basically done by reading system configuration or writing dead code. Actually, it can be achieved through Java SPI mechanism.

The implementation of slf4J is completely different from the above. It uses static binding, which can avoid the problem of classLoad. However, various bridging packages should be introduced when using slf4J. If two opposite bridging packages are introduced, it will lead to cyclically dependent Stack Overflow.