让你10分钟彻底了解Java中混乱的日志体系

时间:2022-04-07 09:00:02

日志框架分为三大部分,包括日志门面、日志适配器、日志库。利用门面设计模式,即Facade来进行解耦,使日志使用变得更加简单,如下图:

让你10分钟彻底了解Java中混乱的日志体系

日志门面

门面设计模式是面向对象设计模式中的一种,日志框架采用的就是这种模式,类似JDBC的设计理念。它只提供一套接口规范,自身不负责日志功能的实现,目的是让使用者不许奥关注底层具体是哪个日志库来负责日志打印及具体的使用字节等。目前用得最广泛的日志门面有 slf4j和commons-logging。

日志库

它具体实现了日志的相关的功能,主流的日志库有三个: log4j、log-jdk、logback。最早java 要想记录只能通过System.out 或 System.err 来完成,非常不方便。log4j 就是为了解决这一问题而提出的,它是最早诞生的日志库。接着JDK也在1.4 版本引入一个日志库 java.util.logging.Logger,简称 log-jdk。这样市面上就出现两种日志功能的实现,开发者在使用时需要关注所使用的日志库的具体字节。logback是最晚出现的,它与log4j出自同一个作者,是log4j的升级版且本身就实现了 slf4j 的接口。

日志适配器

日志适配器分为两种场景:

(1) 日志门面适配器,因为slf4j 规范是后来提出的,在此之前的日志库没有实现 slf4j 的接口的,例如 log4j, 所以,在工程里要想使用slf4j + log4j 的模式,就额外需要一个适配器(slf4j + log4j2) 来解决接口不兼容的问题。

(2) 日志适配器,在一些老的工程,一开始为了开发简单而直接使用了日志库API 来完成日志打印,随着时间的推移想将原来直接调用日志库的模型改为业界标准的门面模式 ( 例如 slf4j +logback 组合) ,但老工程代码里的打印日志的地方太多,难以改变,所以需要一个适配器来完成从旧日志库的API到slf4j的路由,这样在不改动原有代码的情况下也能使用slf4j来统一管理日志,而且后续*替换具体日志库也不成问题。

1、Apache Commons Logging(JCL)

 

Commons本身只提供日志接口,具体实现在运行时 动态寻找对应组件?思路上类似于JDBC的抽象。

JCL动态查找(绑定)日志组件原理如下:

让你10分钟彻底了解Java中混乱的日志体系

JCL为每一种日志采用了一个适配器,具体采用哪一个,是动态根据指定顺序查找classpath是否存在相应日志的实现,如果JCL运行时没有找到任何一种第三方的日志实现,则就使用jdk14自带的java.util.logging(JUL)。

Spring日志就是采JCL,解决了应用程序和框架日志不统一的问题,动态去寻找(应用程序配置)日志体系的实现。

默认的LogFactory是按照下列的步骤去发现并决定哪个日志工具将被使用的,LogFactory按照顺序寻找,找到第一个工具后中止。

  1. 首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类;
  2. 如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类;
  3. 否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类;
  4. 否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类);
  5. 否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog;

2、SLF4j(Simple Logging Facade for java)

 

与JCL类似,本身不提供具体实现,只对外提供接口或者门面,因此它不是具体的日志解决方案,而是通过Facade Pattern 门面模式对外提供一些 java logging api,这些对外提供的核心API就是一些接口以及LoggerFactory的工程类。

与Common logging 不同的是其采用在ClassPath下寻找一下jar包来表示具体的采用哪种实现

  1. slf4j-log4j12.jar(表示指定logh4j)
  2. slf-jdk14.jar(表示使用JUL)
  3. slf4j-jcl.jar(表示使用JCL)
  4. log4j-slf4j-impl.jar(表示指定log4j2)
  5. logback-classic(logback)

SLF4j 静态绑定日志组件原理:

让你10分钟彻底了解Java中混乱的日志体系

3、常见的日志组件

 

让你10分钟彻底了解Java中混乱的日志体系

4. 适用场景

 

4.1 slf4j 使用log4j打印日志

  1. slf4j-api.jar
  2. slf4j-log4j12.jar
  3. log4j.jar

JCL方式的common-logging 是动态查找绑定

SLF4j 是静态绑定,需要加上桥接包,如 SLF4j-log4j2

可通过如下配置进行集成

  1. <!--slf4j门面 -->  
  2.        <dependency>  
  3.            <groupId>org.slf4j</groupId>  
  4.            <artifactId>slf4j-api</artifactId>  
  5.            <version>${slf4j-api.version}</version>  
  6.        </dependency>  
  7.        <dependency>  
  8.            <groupId>org.slf4j</groupId>  
  9.            <artifactId>slf4j-log4j12</artifactId>  
  10.            <version>${slf4j-log4j12.jar.version}</version>  
  11.        </dependency>  
  12.        <dependency>  
  13.            <groupId>log4j</groupId>  
  14.            <artifactId>log4j</artifactId>  
  15.            <version>${log4j.version}</version>  
  16.        </dependency>  

如果是老代码中直接使用了 log4j 日志库提供的接口来打印日志,则还需要引入日志库适配器,配置实例如下所示:

  1. <dependency>  
  2.             <groupId>org.slf4j</groupId>  
  3.             <artifactId>log4j-over-slf4j</artifactId>  
  4.             <version>${log4j-over-slf4j.version}</version>  
  5.         </dependency>  

4.2 slf4j 使用logback打印日志

让你10分钟彻底了解Java中混乱的日志体系

如果是新工程,则推荐使用slf4j+logback 模式,因为logback 自身实现了 slf4j的接口,无须额外引入适配器,另外,logback 是 log4j 的升级版,具备比log4j更多的优点,可通过如下配置进行集成:

  1. <dependency>  
  2.      <groupId>org.slf4j</groupId>  
  3.      <artifactId>slf4j-api</artifactId>  
  4.      <version>${slf4j.version}</version>  
  5.  </dependency>  
  6.   
  7.  <dependency>  
  8.      <groupId>ch.qos.logback</groupId>  
  9.      <artifactId>logback-core</artifactId>  
  10.      <version>${logback.version}</version>  
  11.  </dependency>  
  12.   
  13.  <dependency>  
  14.      <groupId>ch.qos.logback</groupId>  
  15.      <artifactId>logback-classic</artifactId>  
  16.      <version>${logback.version}</version>  
  17.  </dependency>  

4.3 spring 集成log4j2

我们应用使用的是log4j2打印日志;而Spring采用的JCL中不包含log4j2,运行时,JCL从ClassPath下寻找日志的实现,如果没有引用其他实现,最终会使用JUL实现。如下图:

让你10分钟彻底了解Java中混乱的日志体系

这时候会出现什么问题呢?

Spring打印日志和应用程序的打印日志不统一,错误排除还比较困难,而且应用程序和Spring框架,日志不统一,太乱了。

为了让Spring和我们的应用程序,采用统一的log4j2日志体系,需要加入适配器,改善上面应用程序和框架日志的统一问题(加入适配器后),如下图:

让你10分钟彻底了解Java中混乱的日志体系
  1. <dependencies>  
  2.         <!--slf4j门面 -->  
  3.         <dependency>  
  4.             <groupId>org.slf4j</groupId>  
  5.             <artifactId>slf4j-api</artifactId>  
  6.             <version>1.7.25</version>  
  7.         </dependency>  
  8.        <!-- SLF2j-log4j2的桥接-->  
  9.         <dependency>  
  10.             <groupId>org.apache.logging.log4j</groupId>  
  11.             <artifactId>log4j-slf4j-impl</artifactId>  
  12.             <version>2.3</version>  
  13.         </dependency>  
  14.   
  15.       <!-- jcl 桥接 SLF4j -->  
  16.         <dependency>  
  17.             <groupId>org.slf4j</groupId>  
  18.             <artifactId>jcl-over-slf4j</artifactId>  
  19.             <version>1.7.13</version>  
  20.         </dependency>  
  21.   
  22.        <!-- log4j2 -->  
  23.         <dependency>  
  24.             <groupId>org.apache.logging.log4j</groupId>  
  25.             <artifactId>log4j-core</artifactId>  
  26.             <version>2.3</version>  
  27.         </dependency>  
  28.         <dependency>  
  29.             <groupId>org.apache.logging.log4j</groupId>  
  30.             <artifactId>log4j-api</artifactId>  
  31.             <version>2.3</version>  
  32.         </dependency>  
  33.         <dependency>  
  34.             <groupId>com.lmax</groupId>  
  35.             <artifactId>disruptor</artifactId>  
  36.             <version>3.3.4</version>  
  37.         </dependency> 

SLF4j集成其他日志框架的方法:

让你10分钟彻底了解Java中混乱的日志体系

5、优秀实践

 

在使用SLF4J使用时,需要注意以下事项:

  1. private static final Logger LOGGER = LoggerFactory.getLogger(XXX.class);   

logger被定义为static 变量,是因为这个LOGGER与当前类绑定,避免每次都new一个新对象,造成资源浪费。

原文链接:https://www.toutiao.com/a6991357371033371139/