前言
本篇文章记录对Log4j
,Logback
,Log4j2
和Slf4j
日志框架的结构原理的学习。
正文
一. 整体结构
如果单独使用Log4j
,Logback
,Log4j2
日志框架来进行日志打印,那么使用方式可以如下所示。
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
进行日志打印,但是如果要将Log4j
与Slf4j
进行整合使用,还需要引入下述桥接包。
<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>
无论使用哪种日志框架,核心的抽象概念都为Logger
,Appender
和Formatter
,其中Logger
提供日志打印行为,Appender
配置日志的打印位置,Formatter
配置日志的打印格式,这三者的抽象类图可以由下述类图描述。
由于Log4j
和Log4j2
无法满足Slf4j
定义的接口,所以为了在不修改已有代码的情况下还能提供对Slf4j
定义的接口的实现,Log4j
和Log4j2
还需要引入桥接包才能和Slf4j
进行整合使用。整个结构可以用下图进行概括。
二. 源码简析
本小节将从LoggerFactory.getLogger(类.class)
出发,分析Slf4j
是如何决定使用哪种日志框架的。
LoggerFactory
的getLogger()
方法如下所示。
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
接口的实现类,Log4j
,Log4j2
的桥接包以及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
的初始化,然后再调用StaticLoggerBinder
的getLoggerFactory()
方法得到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
接口的实现类。
整个调用时序图如下所示。
总结
由于单独使用Log4j
,Logback
和log4j2
日志框架时,每个日志框架的使用各不相同,所以为了减少切换日志框架时造成的代码入侵,采用了Slf4j
作为日志框架的统一门面,所以可以认为Slf4j
定义了日志框架的接口,每种日志框架提供接口的实现。
同时,由于Log4j
和Log4j2
无法满足Slf4j
定义的接口,所以如果要将Log4j
和Log4j2
与Slf4j
整合使用,还需要引入Log4j
和Log4j2
的桥接包,以达到既能完成与Slf4j
整合,还能不修改原有日志框架的代码的功能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。