一、介绍
Simple Logging Facade for Java(SLF4J)用作各种日志框架(例如java.util.logging,logback,log4j)的简单外观或抽象,允许最终用户在部署时插入所需的日志框架。
要切换日志框架,只需替换类路径上的slf4j绑定。 例如,要从java.util.logging切换到log4j,只需将slf4j-jdk14-1.8.0-beta2.jar替换为slf4j-log4j12-1.8.0-beta2.jar
SLF4J不依赖于任何特殊的类装载机制。 实际上,每个SLF4J绑定在编译时都是硬连线的,以使用一个且只有一个特定的日志记录框架。 例如,slf4j-log4j12-1.8.0-beta2.jar绑定在编译时绑定以使用log4j。 在您的代码中,除了slf4j-api-1.8.0-beta2.jar之外,您只需将您选择的一个且只有一个绑定放到相应的类路径位置。 不要在类路径上放置多个绑定。
因此,slf4j 就是众多日志接口的集合,他不负责具体的日志实现,只在编译时负责寻找合适的日志系统进行绑定。具体有哪些接口,全部都定义在slf4j-api中。查看slf4j-api源码就可以发现,里面除了public final class LoggerFactory类之外,都是接口定义。因此,slf4j-api本质就是一个接口定义。
总之,Slf4j更好的兼容了各种具体日志实现的框架,如图:
应用application通过slf4j桥接器桥接不同的日志框架,通过slf4j api进行日志打印的操作。
从本质上来讲slf4j主要是通过不同的桥接器对接了不同的日志框架,具体的日志打印仍然由日志框架去实现。
二、SLF4J的工作流程
SLF4J通过slf4j-api访问桥接器slf4j-log4j12生成LoggerFactory对象,通过LoggerFactory对象的getLogger返回桥接器的LoggerAdapter对象,通过LoggerAdapter的info方法输出日志,整体的执行过程如下图。
- SLF4J的工作流程核心包含获取Logger对象流程和打印日志流程。
- SLFTJ通过slf4j-api调用log4j桥接器slf4j-log4j12的StaticLoggerBinder生成LoggerFactory对象。
- SLFTJ通过slf4j-api的getLogger方法访问桥接器生产Logger对象。
- SLFTJ通过桥接器的Logger对象实现日志的格式化和打印
三、SLF4J的流程源码
1.SPI
使用serviceLoader,实现SPI,加载类路径下实现了slf4j接口的所有日志框架实现。这些日志框架实现实现org.slg4j包下的工厂接口及类接口。
Java SPI机制:ServiceLoader实现原理及应用剖析
2.LoggerFactory
使用门面模式,从实现了slf4j接口的日志框架中找到可以用的框架,找不到,则使用helpers包下【默认的接口实现】
3.Logger
实现日志输出的核心类
4.Marker
给日志打标,可用于日志过滤/筛选等
5.MDC ( Mapped Diagnostic Contexts )
MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
5.二进制兼容性(Binary backward compatibility)
二进制兼容的两个库可以相互替换,而不会引发任何的链接错误(LinkageError)。
这里LocationAwareLogger 是 api包下的一个类,DEBUG_INT等日志等级常量本可以放在EventConstants中,但是为了保证二进制兼容心【新版本库可以在使用代码不编译的情况下代替旧版本库】,将这些日志常量定义在LocationAwareLogger 接口中,保证各个实现类都包含这些常量,提醒相关开发人员在进行新版本开发时不要擅自更改这些常量。
为何需要关心二进制兼容性?
6.门面模式
先从概念上了解一下:门面模式(Facade Pattern),外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。门面模式又称为外观模式,它是一种对象结构型模式。 为了具体说明这个门面模式是如何工作的,这里写了一个简单例子,让我们根据这个例子看看这里面的学问。
上面所说的“高层接口”指的就是slf4j,而子系统则是诸如logging, logback and log4j这种具体实现。
7.装饰器模式-SubstituteLoggerFactory
在case ONGOING_INITIALIZATION:的情况下,此时是因为其他线程已经在初始化LoggerFactory了,为了避免阻塞等待初始化,当其他线程发现有线程已经在初始化时则会返回一个SUBST_FACTORY,该对象是SubstituteLoggerFactory类型,在其上调用getLogger会返回一个临时的SubstituteLogger。
SubstituteLogger是一个装饰者类,所有方法的调用都会交给真正的Logger对象进行处理,如方法所示:
delegate方法的处理也很容易理解,当delegate为空时,则判断createdPostInitialization是否为真,为真则返回NOP_LOGGER,否则通过getEventRecordingLogger返回一个EventRecodingLogger,该Logger会将所有日志包装成事件,放到对应的队列中缓存。
_delegate对象在另一个线程初始化LoggerFactory完成之后,调用org.slf4j.LoggerFactory#fixSubstituteLoggers方法进行注入。
而之前缓存的日志事件也会由该初始化线程调用org.slf4j.LoggerFactory的replayEvents方法由真正的Logger进行重放。
这个设计点使得多线程在初始化LoggerFactory的时候不会阻塞对方,并且也不会影响在初始化结束之前的日志记录。