追踪一下slf4j的工作原理
slf4j
是一个java
界非常流行的日志门面框架,使用它,你可以任意更换底层实现,如logback
、log4j
,而不需要修改代码。那他是如何做到的。
引入slf4j
的包
这里采用最新稳定版,因为只引入slf4j
无法打印日志,这里选择logback
作为底层实现。
1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
测试输出
从LoggerFactory
获取一个Logger
对象,就能使用了。
1
2
3
4
5
6
public static void main(String[] args) {
Logger log = LoggerFactory.getLogger(T1.class);
log.info("info");
log.debug("debug");
log.warn("warn");
}
代码解析
委托给ILoggerFactory
获取过程中采用懒加载模式,为避免多线程堵塞,使用状态位标识初始化时的多种状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//使用类全路径名创建Logger
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
return logger;
}
//通过获取ILoggerFactory工厂,创建Logger
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
/*
判断状态位 INITIALIZATION_STATE,状态位共五种状态
UNINITIALIZED 尚未初始化
SUCCESSFUL_INITIALIZATION 成功初始化
NOP_FALLBACK_INITIALIZATION 无底层实现类
FAILED_INITIALIZATION 初始化报错
ONGOING_INITIALIZATION 初始化中
*/
public static ILoggerFactory getILoggerFactory() {
//这里类似懒汉单例模式,加锁后设置状态位为ONGOING_INITIALIZATION,然后调用初始化代码。这样就只能有一个线程会初始化loggerfactory,其他线程走下面的switch 中的 ONGOING_INITIALIZATION。
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
//初始化成功,返回单例的LoggerFactory
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
//没底层实现时走这里
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
//初始化过程中抛异常,这里也抛异常
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
//初始化中,返回一个代理Factory,此代理Factory能创建代理Logger
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
//这里的INITIALIZATION_STATE的定义,其加了volatile关键字,多线程修改他时,能立刻被看到
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
五种状态
- 1.如果第一次访问,为未初始化状态,
UNINITIALIZED
,则让第一个获得锁的进行初始化,且不会堵塞后来的线程,将状态切换成ONGOING_INITIALIZATION
。 - 2.如果初始化成功,
SUCCESSFUL_INITIALIZATION
,后来的线程可以直接从StaticLoggerBinder
中获取loggerFactory
。 - 3.如果没有底层实现类,
NOP_FALLBACK_INITIALIZATION
,则返回NOP_FALLBACK_FACTORY
。 - 4.如果初始化报错了,
FAILED_INITIALIZATION
,则直接抛出异常,阻止程序继续执行。 - 5.如果正在初始化中
ONGOING_INITIALIZATION
,则返回一个代理loggerFactory
,他会把所有的logger
记录在队列中,等待真正初始化完成后,用真实的logger
把队列中的日志输出出来。
其中比较有意思的就是,slf4j
为了在初始化Factory
期间不堵塞程序,设置了ONGOING_INITIALIZATION
状态,在此状态下(初始化中)获取Factory
会返回SubstituteLoggerFactory
。
1
2
3
//初始化中时,返回的临时loggerfactory
static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory();
SubstituteLoggerFactory
实现细节
下面代码是SubstituteLoggerFactory
的getLogger()
方法。
第二行的loggers
是一个Map
,getLogger
方法会返回SubstituteLogger
对象,并缓存到loggers
里面。
1
2
3
4
5
6
7
8
9
//SubstituteLoggerFactory的getLogger方法
synchronized public Logger getLogger(String name) {
SubstituteLogger logger = loggers.get(name);
if (logger == null) {
logger = new SubstituteLogger(name, eventQueue, postInitialization);
loggers.put(name, logger);
}
return logger;
}
SubstituteLogger
实现细节
这个logger是一个代理logger,它包含一个_delegate
对象,当_delegate
为空时,会根据createdPostInitialization
标志位返回NOP_LOGGER
或者EventRecodingLogger
。
EventRecodingLogger
会将日志输出到到eventQueue
里。
createdPostInitialization
默认是false,Logger初始化后该值被设置为true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//SubstituteLogger的info方法,是如何实现的
private volatile Logger _delegate;
private final boolean createdPostInitialization;
public void info(String msg) {
delegate().info(msg);
}
Logger delegate() {
if(_delegate != null) {
return _delegate;
}
if(createdPostInitialization) {
return NOPLogger.NOP_LOGGER;
} else {
return getEventRecordingLogger();
}
}
private Logger getEventRecordingLogger() {
if (eventRecodingLogger == null) {
eventRecodingLogger = new EventRecodingLogger(this, eventQueue);
}
return eventRecodingLogger;
}
delegate()
方法会有三种返回可能,
- 如果存在被代理的
_delegate
,则返回_delegate
- 如果
createdPostInitialization
为true
,则返回一个nopLogger
,否则返回一个EventRecodingLogger
。 nopLogger
会扔掉所有的日志EventRecodingLogger
会将所有日志放入queue
中存储起来。
所以slf4j在初始化阶段的日志也不会丢失,而是存储在queue中,等到初始化完成,会取出来输出。
loggerFactory
初始化过程
核心在bind方法里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private final static void bind() {
try {
//用类加载器加载名称叫做 ‘org/slf4j/impl/StaticLoggerBinder.class’的类,这个字符串是写死在代码里的。因为可能存在多个实现,所以使用Set<URL>
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//调用StaticLoggerBinder的静态方法,StaticLoggerBinder可能不存在而报NoClassDefFoundError下面会捕获这个异常。
StaticLoggerBinder.getSingleton();
//修改状态位为success
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
//将SubstituteLoggerFactory的loggers(map)存储的临时logger获取出来,设置logger中的_delegate变量为真实logger
fixSubstituteLoggers();
//从队列中取出日志,用真实logger输出
replayEvents();
//清理SubstituteLoggerFactory 内map中存储的logger,清理队列
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
//如果报NoClassDefFoundError,可能是没有任何实现类,这时将状态位设为NOP_FALLBACK_INITIALIZATION
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
} else {
failedBinding(ncde);
throw ncde;
}
//如果报NoSuchMethodError,可能是底层实现类没有按照要求实现,将状态位设为FAILED_INITIALIZATION
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
总结
slf4j
会反射org/slf4j/impl/StaticLoggerBinder.class
类,如果发现多个实现类,会输出日志提醒用户存在多个实现,并使用第一个实现类。
他会直接调用StaticLoggerBinder.getSingleton()
,如果不存在org/slf4j/impl/StaticLoggerBinder.class
会抛ClassNotFound
异常,这就表明底层实现不存在。
获取StaticLoggerBinder
实例过后,会调用fixSubstituteLoggers()
修复初始话中创建的临时logger
,会将存储在queue
中的日志重新输出。
一个bug
上面的实现其实存在一个BUG
,核心方法bind()
里面的fixSubstituteLoggers()
方法,replayEvents()
方法只会在存在底层log实现时才会执行,不存在底层实现会抛异常进入cache
代码块。如果这种情况下将导致日志一直输出到Queue
里面,导致OOM
。
1
2
3
4
//这几行代码在没有底层实现时不能执行
fixSubstituteLoggers();
replayEvents();
SUBST_FACTORY.clear();
2020-04 更新
上面提到的bug已经在1.7.30
版本修复,新版本在finally
代码块里面处理的这些逻辑,无论是否初始化成功都会释放Queue
里面的日志,所以不用担心日志不释放的问题。
但极端情况下还会出现问题
1. 线程A执行下面代码,因为第一次执行会触发初始化逻辑:
1
2
3
4
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
2. 线程B在执行下面代码,获取到的ILoggerFactory
是SubstituteLoggerFactory
实例
1
2
3
4
5
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
sleep(10000); //假设线程B执行到这里,让出cpu
return iLoggerFactory.getLogger(name);
}
3. 线程A接着执行,直到LoggerFactory
初始化完成,在方法fixSubstituteLoggers()
里面会将SubstituteLoggerFactory
里面的状态位postInitialization
设为true,并修复创建LoggerFactory
期间生成的的代理Logger
。
4. 接着线程B继续执行,他会拿到ILoggerFactory
对象获取到一个SubstituteLogger
,这个SubstituteLogger
不能得到修复,会恒返回NOP_LOGGER
实例。从而使日志失效。
新版本
1.8
版本以后slf4j
加载底层实现改为java
的SPI
技术做了。请看这个类java.util.ServiceLoader
。