3

前言

本篇文章记录对Log4jLogbackLog4j2Slf4j日志框架的结构原理的学习。

正文

一. 整体结构

如果单独使用Log4jLogbackLog4j2日志框架来进行日志打印,那么使用方式可以如下所示。

  • Log4j
import org.apache.log4j.Logger;

public class Log4jTest {

    private static final Logger logger_log4j
            = Logger.getLogger(Log4jTest.class);

    public static void main(String[] args) {
        logger_log4j.info("Test Log4j info.");
        logger_log4j.warn("Test Log4j warn.");
        logger_log4j.error("Test Log4j error.");
    }

}

单独使用Log4j时,需要引入的依赖如下。

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
</dependency>

只需要引入上述依赖,就可以使用Log4j进行日志打印,但是如果要将Log4jSlf4j进行整合使用,还需要引入下述桥接包。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
  • Logback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {

    private static final Logger logger_logback
            = LoggerFactory.getLogger(LogbackTest.class);

    public static void main(String[] args) {
        logger_logback.info("Test Logback info.");
        logger_logback.warn("Test Logback warn.");
        logger_logback.error("Test Logback error.");
    }

}

Logback单独使用时,只需要引入下述依赖。

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
  • Log4j2
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2Test {

    private static final Logger logger_log4j2
            = LogManager.getLogger(Log4j2Test.class);

    public static void main(String[] args) {
        logger_log4j2.info("Test Log4j2 info.");
        logger_log4j2.warn("Test Log4j2 warn.");
        logger_log4j2.error("Test Log4j2 error.");
    }

}

单独使用Log4j2时,需要引入的依赖如下。

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.1</version>
</dependency>

同理,如果需要与Slf4j进行整合使用,那么还需要引入下述桥接包。

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.17.1</version>
</dependency>

无论使用哪种日志框架,核心的抽象概念都为LoggerAppenderFormatter,其中Logger提供日志打印行为,Appender配置日志的打印位置,Formatter配置日志的打印格式,这三者的抽象类图可以由下述类图描述。

由于Log4jLog4j2无法满足Slf4j定义的接口,所以为了在不修改已有代码的情况下还能提供对Slf4j定义的接口的实现,Log4jLog4j2还需要引入桥接包才能和Slf4j进行整合使用。整个结构可以用下图进行概括。

二. 源码简析

本小节将从LoggerFactory.getLogger(类.class)出发,分析Slf4j是如何决定使用哪种日志框架的。

LoggerFactorygetLogger()方法如下所示。

public static Logger getLogger(Class<?> clazz) {
    Logger logger = getLogger(clazz.getName());
    
    ......
    
    return logger;
}

public static Logger getLogger(String name) {
    // ILoggerFactory接口的实现类由Log4j,Log4j2的桥接包以及Logback提供
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

getLogger()方法中会调用到getILoggerFactory()方法来获取到ILoggerFactory接口的实现类,Log4jLog4j2的桥接包以及Logback都提供了ILoggerFactory接口的实现类,所以下面继续跟进getILoggerFactory()方法。

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                // 执行绑定逻辑,初始化StaticLoggerBinder
                // Log4j,Log4j2的桥接包以及Logback都会提供StaticLoggerBinder
                performInitialization();
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        // 通过StaticLoggerBinder得到ILoggerFactory的实现类
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

getILoggerFactory()方法中会先调用到performInitialization()方法来完成StaticLoggerBinder的初始化,然后再调用StaticLoggerBindergetLoggerFactory()方法得到ILoggerFactory,下面跟进performInitialization()方法。

private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        if (!isAndroid()) {
            // 获取所有StaticLoggerBinder的路径
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            // 如果存在多个StaticLoggerBinder的路径,则全部打印出来
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // 这里执行日志框架的绑定
        StaticLoggerBinder.getSingleton();
        // 将初始化状态置为3,表示成功完成StaticLoggerBinder的初始化,也即成功完成日志框架的绑定
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        // 打印实际绑定的是哪个日志框架
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstituteLoggers();
        replayEvents();
        SUBST_FACTORY.clear();
    } catch (NoClassDefFoundError ncde) {
    
        ......
        
    } catch (java.lang.NoSuchMethodError nsme) {
    
        ......
        
    } catch (Exception e) {
    
        ......
        
    }
}

上述bind()方法会从classpath下的jar包中寻找StaticLoggerBinder,然后将所有StaticLoggerBinder的路径存放在集合中,如果存在多个StaticLoggerBinder的路径,则将这些路径信息打印出来,然后完成与StaticLoggerBinder的初始化,一旦StaticLoggerBinder完成初始化,则也完成了与实际的日志框架的绑定,后续通过该日志框架提供的StaticLoggerBinder获取ILoggerFactory,再通过获取到的ILoggerFactory获取到对应日志框架对Logger接口的实现类。

整个调用时序图如下所示。

总结

由于单独使用Log4jLogbacklog4j2日志框架时,每个日志框架的使用各不相同,所以为了减少切换日志框架时造成的代码入侵,采用了Slf4j作为日志框架的统一门面,所以可以认为Slf4j定义了日志框架的接口,每种日志框架提供接口的实现。
同时,由于Log4jLog4j2无法满足Slf4j定义的接口,所以如果要将Log4jLog4j2Slf4j整合使用,还需要引入Log4jLog4j2的桥接包,以达到既能完成与Slf4j整合,还能不修改原有日志框架的代码的功能。


半夏之沫
65 声望31 粉丝