前言
本篇文章记录对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
时,需要引入的依赖如下。
log4j
log4j
1.2.16
只需要引入上述依赖,就可以使用Log4j
进行日志打印,但是如果要将Log4j
与Slf4j
进行整合使用,还需要引入下述桥接包。
org.slf4j
slf4j-log4j12
1.7.25
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
单独使用时,只需要引入下述依赖。
ch.qos.logback
logback-classic
1.2.3
ch.qos.logback
logback-core
1.2.3
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
时,需要引入的依赖如下。
org.apache.logging.log4j
log4j-core
2.17.1
org.apache.logging.log4j
log4j-api
2.17.1
同理,如果需要与Slf4j
进行整合使用,那么还需要引入下述桥接包。
org.apache.logging.log4j
log4j-slf4j-impl
2.17.1
无论使用哪种日志框架,核心的抽象概念都为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 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
整合,还能不修改原有日志框架的代码的功能。