前言本篇文章记录对Log4jLogbackLog4j2和Slf4j日志框架的结构原理的学习。正文一. 整体结构如果单独使用Log4jLogbackLog4j2日志框架来进行日志打印那么使用方式可以如下所示。Log4jimport 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 groupIdlog4j/groupId artifactIdlog4j/artifactId version1.2.16/version /dependency只需要引入上述依赖就可以使用Log4j进行日志打印但是如果要将Log4j与Slf4j进行整合使用还需要引入下述桥接包。dependency groupIdorg.slf4j/groupId artifactIdslf4j-log4j12/artifactId version1.7.25/version /dependencyLogbackimport 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 groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.2.3/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-core/artifactId version1.2.3/version /dependencyLog4j2import 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 groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version2.17.1/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-api/artifactId version2.17.1/version /dependency同理如果需要与Slf4j进行整合使用那么还需要引入下述桥接包。dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-slf4j-impl/artifactId version2.17.1/version /dependency无论使用哪种日志框架核心的抽象概念都为LoggerAppender和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接口的实现类由Log4jLog4j2的桥接包以及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 // Log4jLog4j2的桥接包以及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 { SetURL 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接口的实现类。整个调用时序图如下所示。总结由于单独使用Log4jLogback和log4j2日志框架时每个日志框架的使用各不相同所以为了减少切换日志框架时造成的代码入侵采用了Slf4j作为日志框架的统一门面所以可以认为Slf4j定义了日志框架的接口每种日志框架提供接口的实现。同时由于Log4j和Log4j2无法满足Slf4j定义的接口所以如果要将Log4j和Log4j2与Slf4j整合使用还需要引入Log4j和Log4j2的桥接包以达到既能完成与Slf4j整合还能不修改原有日志框架的代码的功能。