Logback解析
Logback解析
引言
日志在排除线上问题、跟踪线上系统运行情况中发挥着重要的作用。在java应用的开发中,有许多的日志框架。
这些日志框架大致可以分为两类,一类是日志门面(JCL、slf4j),定义日志的抽象接口。另一类是日志的实现(JUL、log4j、log4j2、logback),负责真正的处理日志。
日志框架的发展的前因后果
为什么会有这么多的日志框架,从Java日志框架的发展史里大概可以一探究竟。
1 | log4j是Java社区最早的日志框架,推出后一度成为Java的事实日志标准,据说Apache曾建议Sun把log4j加入到Java标准库中,但是被Sun拒绝。 |
日志框架的选择
那么面对这些日志框架,该如何选择呢?如果你是在开发一个新的项目(类库)而不是维护一个上古的遗留代码,那么在打印日志时推荐使用日志门面,秉承面向接口编程的思想,与具体的日志实现框架解耦,这样日后可以很容易地切换到其他的日志实现框架。
特别是当你的代码以SDK的方式提供给别人使用时,使用日志门面能避免使用方可能出现的日志框架冲突问题。如果你的SDK里使用了log4j,而使用方的应用里使用的logback,这时使用方就不得不分别针对log4j和logback维护两套日志配置文件,来确保所有日志正常的输出。
在目前已有的两个日志门面框架中,slf4j规避了JCL在部分场景下因为ClassLoader导致绑定日志实现框架失败的问题;能支持以上提到的所有日志实现框架;且slf4j支持占位符功能,在需要拼接日志的情况在接口层面就比JCL有更好的性能,所以推荐使用slf4j,下面简单多介绍下slf4j。
1 | // slf4j的占位符功能 |
slf4j-api

slf4j-api为各种日志框架提供了一个统一的接口,使用户可以用统一的接口来记录日志,但是可以动态的决定真正的实现框架。logback、log4j、common-logging等都实现了这个接口。所以阅读logback源码之前,首先看看logback是如何与slf4j对接的。

slf4j-api最重要的是三个接口和一个入口类。
三个接口分别是org.slf4j.ILoggerFactory、org.slf4j.Logger和org.slf4j.spi.LoggerFactoryBinder。
入口类是org.slf4j.LoggerFactory。
org.slf4j.ILoggerFactory接口只提供了一个获取Logger的动作,由具体的日志框架实现者去实现该接口。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* ILoggerFactory logger工厂接口
* 提供获取Logger动作
* 由具体的日志框架实现者去实现该接口
*/
public interface ILoggerFactory {
/**
* 获取Logger
*
* @param name the name of the Logger to return
* @return a Logger instance
*/
public Logger getLogger(String name);
}org.slf4j.Logger接口提供了打印不同等级日志的动作和获取loggerName等动作。
org.slf4j.spi.LoggerFactoryBinder接口提供了获取ILoggerFactory的实现类的动作。一个内部接口去帮助LoggerFactory绑定合适的ILoggerFactory的实例。由指定路径的实现类
org.slf4j.impl.StaticLoggerBinder去实现该接口,每一个实现slf4j门面的日志框架都有一个实现该接口的org.slf4j.impl.StaticLoggerBinder。1
2
3
4
5
6public interface LoggerFactoryBinder {
public ILoggerFactory getLoggerFactory();
public String getLoggerFactoryClassStr();
}org.slf4j.LoggerFactory是一个入口类,通过调用ILoggerFactory的实现类获取Logger对象,稍后我们分析LoggerFactory的源码。首先看一个简单的例子:
1
2
3
4
5
6public class HelloWorld1 {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
logger.debug("Hello world.");
}
}不管实现框架是什么,要获取Logger对象,都是通过这个LoggerFactory的getLogger()方法,所以这个类也非常重要。具体实现框架和slf4j的对接,就是通过这个类LoggerFactory,让我们来看看这个类是如何对接slf4j的。
getLogger(String name)
获取ILoggerFactory的实现类,并调用getLogger(name)方法返回Logger对象。
1 | public static Logger getLogger(String name) { |
getILoggerFactory()
首先用一个静态变量INITIALIZATION_STATE标识初始化状态,初始值=0,用双重检查锁判断这个状态,如果没有初始化,则将状态置为1,并且调用performInitialization()方法去初始化。
当初始化状态=1,则调用
StaticLoggerBinder.getSingleton().getLoggerFactory()获取ILoggerFactory接口的实现类。当初始化状态=4,则意味着没有可绑定的实现框架,返回一个空的ILoggerFactory接口实现类NOPLoggerFactory。内部getLogger(name)方法返回一个NOPLogger对象,其中打印方法中什么也没做。
当初始化状态=2,则初始化过程中发生异常,直接抛出IllegalStateException()异常。
当初始化状态=3,则正在进行初始化,返回一个替代工厂SubstituteLoggerFactory。
1 | static final int UNINITIALIZED = 0; //未初始化 |
1 | static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory(); //替代工厂 |
1 | /** |
performInitialization()
- 调用bind()执行绑定。
- 如果初始化成功,则进行版本检查。
1 | private final static void performInitialization() { |
bind()
- 首先是检查classpath里是否存在多个日志框架,如果有就会抛出警告信息,提示用户只保留一个。
- 这段代码提到了一个最关键的
StaticLoggerBinder类,这个类实现LoggerFactoryBinder接口,提供获取ILoggerFactory实现类的方法,检查是否有这个类存在,以及这个类有没有getSingleton()方法,如果有,就视为绑定成功。其实这个类还必须有getLoggerFactory()方法,否则虽然绑定成功,但是到了运行期,一样会抛出NoSuchMethodException。我认为这里是slf4j设计不好的地方,应该在bind()方法里,就检查一下StaticLoggerBinder有没有实现getLoggerFactory()方法。
1 | private final static void bind() { |
总结一下大致流程
- LoggerFactory通过StaticLoggerBinder获取ILoggerFactory的实现类。
- 通过ILoggerFactory的实现类获取Logger的实现类返回。