

日志框架的类别
门面型
面对市面多种框架的出现,也出现了如同JDBC是一种日志api标准,其作用是在不改动api的前提下切换日志的实现。主要有:
JCL(Commons Logging) Apache基金会推出的一套JAVA日志接口。 SLF4J(Simple Logging Facade for Java) ceki推出的一套日志门面接口,通过桥接方式对接各种日志实现,slf4j的简洁设计思想以及对各种日志框架的兼容使其成为主流门面日志框架,其本身只提供一个slf4j-api-vesion.jar包,提供业务使用的日志抽象接口。针对不同的日志实现框架封装了不同的桥接组件,通过引用这些桥接组件灵活的选择适合的日志记录框架。
2.记录型
真正的日志实现,我们通常会根据框架的性能、功能等方面选择其中一种。主要有:
Log4j:由ceki首创,后成为Apache基金会顶级项目的日志框架,后被log4j2替代。
Jul(Java Util Logging): JDK在1.4版本推出的日志记录工具。
Logback:由ceki推出一个“可靠、通用、快速而又灵活的Java日志框架”,其直接实现了slf4j接口。
log4j2:由apache基金参考logback并做一系列优化推出的日志实现框架,且与log4j不兼容。

桥接组件介绍

桥接过程源码分析
1. 核心类介绍
org.slf4j.Logger: 主要调用者入口,提供丰富的日志记录方法。
org.slf4j.ILoggerFactory: 提供获取Logger实例方法,具体工厂类实现由桥接组件实现。
org.slf4j.LoggerFactory:ILoggerFactory的包装类,在编译时与各种日志组件ILoggerFactory实现绑定。其默认绑定实现是NOPLoggerFactory。
org.slf4j.impl.StaticLoggerBinder:绑定ILoggerFactory实例与LoggerFactory;由各个桥接包实现并定义在org.slf4j.impl包下,注意在slf4j-api-version.jar中不存在该类。
org.slf4j.helpers.NOPLogger:Slf4j的默认日志实现,不输出任何日志
2. 源码分析 - 无日志实现依赖
gradle 依赖 - 仅依赖slf4jApi
dependencies {implementation 'org.slf4j:slf4j-api:1.7.33'}
应用程序入口

通过org.slf4j.LoggerFactory#getLogger静态方法获取Logger实例

通过LoggerFactory#getILoggerFactory方法获取具体Logger工厂实现类

首次调用,通过LoggerFactory#performInitialization方法初始化,并通过LoggerFactory#bind方法进行实现类绑定

通过ClassLoader加载org/slf4j/impl/StaticLoggerBinder.class,因为没有依赖任何桥接包,因此加载不到任何StaticLoggerBinder。


因为不存在StaticLoggerBinder类,抛出NoClassDefFoundError异常,并将state设置成4(使用默认的NOPLogger),同时可在console看到相应日志。


根据status返回默认的NOPLoggerFactory


最终应用入口使用的Logger是由NOPLoggerFactory#getLogger获取的NOPLogger,其内部所有日志输出方法均为空实现。

gradle依赖 - 添加logback桥接包的依赖
dependencies {implementation 'org.slf4j:slf4j-api:1.7.33'// 内部依赖logback-coreimplementation 'ch.qos.logback:logback-classic:1.2.10'}
前面逻辑同上,我们直接看bind()方法中加载staticLoggerBinder的逻辑。可见staticLoggerBinder被ClassLoader加载到,同时state设置为3(成功)。

使用单例的方式从StaticLoggerBinder具体实现类获取LoggerFactory

最终应用入口使用的logger是ch.qos.logback.classic包的loggerContext(implements ILoggerFactory),使用默认格式输出如下:


源码分析 - 桥接多种实现
我们的系统可能存在多层依赖,而每种依赖中可能依赖了自己选择的日志实现,那么这种存在多种日志实现,slf4j会怎么处理呢?
gradle依赖 - 添加jul、log4j2、logback日志实现桥接包的依赖
dependencies {implementation 'org.slf4j:slf4j-api:1.7.33'// 桥接logbackimplementation 'ch.qos.logback:logback-classic:1.2.10'implementation 'org.apache.logging.log4j:log4j-core:2.17.1'// 桥接log4j2implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1'// 桥接jdk-logimplementation 'org.slf4j:slf4j-jdk14:1.7.33'}
我们依旧看bind()方法中加载staticLoggerBinder的逻辑。ClassLoader加载到3个staticLoggerBinder类(console会输出multiple bind警告),由StaticLoggerBinder.getSingleton()可见,程序具体使用哪个StaticLoggerBinder实现由JVM随机选择(我们尽量剔除没必要的依赖,以免出现魔幻问题)。


最终应用入口使用的logger是ch.qos.logback.classic包的loggerContext,默认输出格式为如下:

Springboot (2.3.x)默认日志实现
从springboot-start-logging包可见其默认依赖的日志实现应该是logback。

应用系统输出日志可见,其使用的也是logback日志实现。

参考资料
slf4j官网:https://www.slf4j.org/
slf4j官方桥接介绍:https://www.slf4j.org/legacy.html
logback官网:https://logback.qos.ch/
JCL官网:https://commons.apache.org/proper/commons-logging/
Log4j2官网:https://logging.apache.org/log4j/2.x/











