大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
本篇文章记录对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整合,还能不修改原有日志框架的代码的功能。
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。