一文读懂Java日志系统与SLF4J桥接关系

一、Java日志系统发展史

Java日志相关组件可以说是五花八门,各有千秋。除了各种日志框架,例如log4j、logback等,还有各种日志门面,令人眼花缭乱。本文将梳理Java日志框架发展史,并给大家带来其中最流行的 SLF4J日志门面的桥接关系图。

1.1 System.out 与 System.error

最开始,开发者打印日志用的是System.out.println()或者System.err.println()

其中,System.out标准输出流(stdout),System.err错误输出流(stderr)。所以在许多IDE中,System.err的输出字体颜色是红色,以示警醒;而System.out是正常的颜色。

举个例子,对于以下这段代码:

public class MyLog {
    public static void main(String[] args) {
        System.out.println("this is out.");
        System.err.println("this is err.");
    }
}
复制代码

当我们对输出重定向时,System.outSystem.err对应了不同的Linux输出流。

# 1. 直接运行代码,将在控制台看到两个输出
$ javac MyLog.java && java MyLog
this is out.
this is err.

# 2. 重定向错误输出流,将在控制台看到“this is out.”,在error.log中看到“this is error.”
$ javac MyLog.java && java MyLog 2>error.log
this is out.

# 3. 重定向标准输出流,将在控制台看到“this is error.”,在info.log中看到“this is out.”
$ javac MyLog.java && java MyLog >info.log 
this is err.

# 4. 同时重定向stdout和stderr,将在info.log中看到两个输出
$ javac MyLog.java && java MyLog >info.log 2>&1
# 控制台无输出
复制代码

也正因为如此,如果在代码中同时使用这两种方式打印,我们在控制台上看到的结果与代码顺序其实并不一致。

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 400; i++) {
        System.out.println(i);
        System.err.println(i);
    }
}
复制代码

输出结果: image.png

1.1.1 存在的问题

  1. 日志输出是同步的,需要获取锁。在大量日志输出时,产生锁竞争,可能会阻塞线程。
  2. 不同开发者会定义不同日志格式,在庞大的开发团队或者引入众多第三方jar包时,打印的日志会混乱不堪。

1.2 Log4J 的诞生

为了避免上述混乱,EU SEMPER (Secure Electronic Marketplace for Europe,欧洲安全电子市场) 项目组于1996年开始着手开发一个程序追踪API。经过无数次优化之后,这套API进化成了 Log4J框架,并于1999年公之于众。

后来Log4J成为了Apache基金会项目的一员,它的作者Ceki也随之加入了Apache。

1.3 JUL的 诞生

Sun公司的Java的母公司。2002年,JDK1.4版本发布,Sun首次推出了日志库 java.util.logging (JUL)包,其实现基本模仿了Log4j的实现。不过此时 Log4J已经比较成熟,所以在开发者的选择上,Log4J占有一定优势。

1.4 JCL诞生

如果一个系统仅由一个开发者开发,那开发者可以选择只一个合适的日志库。但如果项目要引入各种第三方jar包,而不同jar包可能使用的是不同的日志库(例如Log4J或者JUL),这样一来打印的日志依旧是混杂着不同格式的。

故在2002年,Apache推出了日志门面JCL(Jakarta Commons Logging,虽然后来改名成了Apache Commons Logging,但依旧习惯于称它为JCL)。JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,这样,在你应用代码里,只需调用JCL的接口,底层实现可以是Log4j,也可以是JUL。

但由于Maven和Tomcat历史版本的设计原因,倘若开发者引入依赖时遗漏了JCL的jar包,可能会导致Tomcat内存泄漏,迫使程序跑着跑着就崩溃了。虽然在后续的版本中修复了这个问题,但人们对它还是心有余悸。

1.5 SLF4J/Logback 诞生

2006年,Ceki因无法适应Apache的工作,选择了离开。之后,Ceki便着手开发了Logback日志框架和SLF4J日志门面。

SLF4J与JCL是直接的竞争对手。但不同于JCL,是在运行时动态加载日志组件的,SLF4J需要开发者明确地引入桥接包。并且,Ceki自己实现了一系列的桥接包来帮助SLF4J与其他日志库建立关系。

这样一来,不论第三方jar包使用的是什么日志框架,只要我们应用中合理使用桥接包,都可以转化为统一的日志格式。

1.6 Log4j2

Apache眼看有被 Logback 反超的势头,于2012年完全重写了Log4j 1.x,推出了Log4J 2.x。Log4j2具有Logback的所有特性,并且也提供了桥接包,故可以使用 JCL 与 SLF4J 的API。

1.7 现状与总结

经过长久的发展,日志组件现状如下:

  • Log4j 1.x直到现在仍被许多项目使用,不过早在2015年,官方已经停止了维护。并且Log4j 1.x最有用的特性之一在Java 9中也无法使用。
  • SLF4J/Logback 到现在仍被众多开发者使用。
  • Log4j 2.x也提供了桥接包,这样开发者可以使用 JCL 或者 SLF4J 日志门面作为API,后台使用Log4j2为日志框架。
  • JCL仍然存在,但自2014年后也从未更新过。

image.png

二、SLF4J桥接包

日志框架与桥接包众多,笔者特地画了张图,可以清晰地表示主流日志门面与日志框架所有关系。 SLF4J桥接包关系图

  1. 其中方框里的是jar包,可直接在Maven中心仓库中搜索,引入需要的版本。
  2. 在Logback中,并没有对应的桥接器,因为是同一个作者写的,所以已经适配SLF4J了。
  3. 注意在使用slf4j日志门面之后,只能指定一个slf4j的适配库。在引入其他包的时候,如果有日志冲突,需要使用 <exclusion>...</exclusion> 来去掉包。
  4. 一个日志框架对应的适配器和桥接器不能同时引入,否则会类似于死循环,导致栈溢出 StackOverflowError。例如slf4j-log4j12 和 log4j-over-slf4j 同时引入。

三、实践例子

假设现在有3个项目A、B、C。 其中A使用 JCL的简单实现;B使用 Log4j;C使用 Log4j2。现在我们想统一到使用 Log4j2,怎么办呢?

只需要在SLF4J桥接包关系图中找连接关系,对应到 Log4j2即可。

shuiyin2.jpg

问题来了,为什么我们项目C不直接使用 Log4j2的包,而是通过SLF4J这一层呢?因为实际开发中,推荐使用日志门面去调用日志接口,这样在转换日志实现时,仅需引入适配器即可,更加方便与高效。

四、参考


你的点赞和关注是对我最大的鼓励!
我的往期文章:
浅析认证授权的三种方式(session、token、JWT)
浅析如何保证数据库与缓存一致性

猜你喜欢

转载自juejin.im/post/7078293070751465508
今日推荐