Java 日志框架梳理

Java 日志相关框架五花八门,索性梳理下日志相关组件

一、为什么会有日志框架

为什么会有日志框架?我直接用 System.out 不行吗?

  1. 性能考虑System.out 是一个同步方法,会阻塞线程执行,高并发情况下严重影响性能;日志框架通常异步记录日志
  2. 日志级别:日志库允许我们根据日志的重要性设置不同的日志级别,如 DEBUG、INFO、WARN、ERROR 等。这样我们可以根据需要调整显示的日志级别,比如在开发环境中我们可能需要所有级别的日志,而在生产环境中我们可能只关心 WARN 和 ERROR 级别的日志。
  3. 日志持久化:使用 System.out.println 输出的日志信息,一旦程序结束,日志信息就会丢失。而日志库可以配置将日志信息输出到文件,这样就可以永久保存日志信息,方便后续查看和分析。
  4. 灵活性:日志库提供了丰富的配置选项,我们可以自定义日志的输出格式,输出目标(控制台、文件、数据库等),甚至可以根据日志级别选择不同的输出目标。
  5. 集成性:如果你的代码被其他人用作库或框架,那么使用日志库可以让最终的用户选择他们自己喜欢的日志系统。

二、日志框架分类

日志框架分为日志门面框架和日志实现框架

2.1 日志门面框架

所谓日志门面框架,就是提供了日志接口,但是没有提供具体实现,这样的好处是可以根据自己的需要更换底层日志实现,而不用变动代码,日志门面框架有两个:

  • JCL:Jakarta Commons Logging 后面改名为 Apache Commons Logging,看官网从 2014 年后就再也没更新了
  • Slf4j:Simple Logging Facade for Java,是目前主流的日志门面框架

    2.2 日志实现框架

  • Log4J:第一代日志框架,现在不咋用了
  • JUL:模仿 Log4J 写的, java.util.logging 在 jDK 中可以直接使用,无需引入依赖
  • Logback:和 Slf4j 一起推出,是同一个人写的,也是目前最常用的组合即 Slf4j + Logback
  • Log4j2:完全重写 Log4j1.x,和 Logback 竞争,有 Logback 全部特性,目前也有 Slf4j + Log4j2 的组合

    2.3 总结

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

三、关于 Slf4j 的众多桥接包

3.1 桥接包关系图

3.2 使用说明

  1. 日志框架使用时,推荐用日志门面去调用日志实现,这样后续想使用其他日志实现,引入日志实现包和对应适配器即可
  2. 桥接包和适配器包的区别:桥接包是日志实现把数据传入日志门面,适配器是由日志门面把数据传给具体日志实现
  3. 不能同时引入同一个日志实现框架适配器包桥接包,会造成循环调用,桥接包是日志实现把数据传入日志门面的,适配器包是日志门面把数据传给具体日志实现的,如果同一个实现框架的话,自然就循环调用了
  4. 注意在使用 slf4j 日志门面之后,只能指定一个 slf4j 的适配库。在引入其他包的时候,如果有日志冲突,需要使用 <exclusion>...</exclusion> 来去掉包。
  5. 在 Logback 中,并没有对应的桥接器,因为是同一个作者写的,所以已经适配 SLF4J 了

3.3 无痛更换日志实现(实战)

场景:现有一个项目是使用的 JUL 实现,我们想要更换成 Logback 实现,如何做?
思路:引入 jul-to-slf4j 桥接器,就可以把日志数据传入到门面去执行,再引入 Logback 依赖,这样门面就会调用 Logback 去执行
具体操作

  1. 添加 SLF4J 和 Logback 的依赖:首先,需要在的项目中添加 SLF4J 和 Logback 的依赖。这样,项目就可以使用 SLF4J 的 API,并且 SLF4J 的日志消息会被 Logback 处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!-- SLF4J的API -->
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.26</version>
    </dependency>
    <!-- Logback的核心库 -->
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    </dependency>
  2. 添加 JUL-to-SLF4J 的桥接器:然后,需要添加 JUL-to-SLF4J 的桥接器的依赖。这个桥接器会将 JUL 的日志消息转发到 SLF4J。

    1
    2
    3
    4
    5
    6
    <!-- JUL到SLF4J的桥接器 -->
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.26</version>
    </dependency>
  3. 在代码中安装桥接器:最后,需要在代码中安装这个桥接器。可以在主类或者Web应用的初始化代码中添加以下代码:

    1
    2
    3
    // 安装JUL到SLF4J的桥接器
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();

这样,项目中原本使用 JUL 进行日志记录的代码就会将日志消息发送到 SLF4J,然后 SLF4J 再将日志消息发送到 Logback 进行处理。这个过程不需要修改原有的日志记录代码,只需要添加一些配置和初始化代码即可。

以上完整代码:https://github.com/hczs/java-log-demo

参考:

  1. https://github.com/DavidSuperM/davidsuperm.github.io/blob/master/java/%E6%97%A5%E5%BF%97%E6%A1%86%E6%9E%B6%E9%80%89%E5%9E%8B%E4%B8%8E%E4%BD%BF%E7%94%A8.md
  2. https://developer.aliyun.com/article/768396
  3. https://juejin.cn/post/7078293070751465508#heading-0