日志-Log4j,Logback日志框架的结构原理-元一软件
2026/7/12 22:49:49 网站建设 项目流程

前言

本篇文章记录对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整合,还能不修改原有日志框架的代码的功能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询