一、引言

对于应用程序来说,日志的重要性不言而喻。而Java日志技术存在多种日志框架,就目前常用的主流的日志框架包括:Log4j,Log4j2,Commons Logging(JCL),Slf4j,Logback,JUL

说实话,个人觉得Java的日志体系有点混乱,我曾经去研究过这个知识点,但后来有点遗忘,但用起来没有问题,但一直有想法想记录一下这个学习过程,现在终于有这个机会了,下面就从Java日志历史说起吧。

二、Java日志历史

下面先来简单介绍一下常用的日志框架,做一个了解吧,后面会具体用代码来说明。

1、常用日志框架介绍

Log4j :Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目,Log4j是几种Java日志框架之一。可以不需要依赖第三方的技术,直接记录日志。

Log4j2

Commons Logging : 是Apache公司开发的一个抽象日志通用框架,本身不实现日志记录,但是提供了记录日志的抽象方法即接口,之前叫Jakarta Commons Logging,也就是JCL,后更名为Commons Logging。JCL不能直接记录日志,需要通过第三方来记录日志。

Slf4j (Simple Logging Facade for Java) : 类似于Commons Logging,是一套简易Java日志门面,本身并无日志的实现。

Logback

JUL(Java Util Logging)

看了上面的介绍是不是觉得比较混乱,这些日志框架之间有什么异同,都是由谁在维护?

2、Java常用日志框架历史
  • 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。
  • 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议sun引入Log4j到java的标准库中,但Sun拒绝了。
  • 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势。
  • 接着,Apache推出了Jakarta Commons Logging(JCL),JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是log4j,也可以是Java Util Logging。
  • 后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了slf4j(日志门面接口,类似于Commons Logging)和Logback(Slf4j的实现)两个项目。
  • 现今,Java日志领域被划分为两大阵营:Commons Logging(JCL)阵营和SLF4J阵营。Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。
  • Apache眼看有被Logback反超的势头,于2012-07重写了log4j 1.x,成立了新的项目Log4j 2。Log4j 2具有logback的所有特性。
3、Java常用日志框架之间的关系
  • Log4j2与Log4j1发生了很大的变化,log4j2不兼容log4j1。
  • Commons Logging和Slf4j是日志门面,log4j和Logback则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用这只需要关注接口而无需关注具体的实现,做到解耦。
  • 比较常用的组合使用方式是Slf4j与Logback组合使用,Commons Logging与Log4j组合使用。
  • Logback必须配合Slf4j使用。由于Logback和Slf4j是同一个作者,其兼容性不言而喻。

三、日志框架代码示例

1、JUL(java.util.logging)

我们先什么依赖都不引入,直接使用JDK自带的日志框架,即JUL,看看打出的日志是神马样子。

java日志分析器 java解析日志工具_slf4j

2、log4j

下面引入log4j的依赖,注意此处用的是1.X版本,我们知道,log4j需要配置文件的,我们先不加,看看是什么效果。没错,报错了,这个错误是不是很熟悉。。。。

<dependency>
	  <groupId>log4j</groupId>
	  <artifactId>log4j</artifactId>
	  <version>1.2.17</version>
	</dependency>

不加配置文件的日志输出,有警告。

java日志分析器 java解析日志工具_java_02


下面加下配置文件:log4j.properties,看看日志输出效果。

log4j.rootLogger=info,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout   
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %p [%t] %C.%M(%L) | %m%n

java日志分析器 java解析日志工具_日志_03


从上面可以知道,JULLog4j是可以直接进行日志输出的。

3、JCL(Jakarta Commons Logging)

下面引入JCL的依赖,其他的依赖先不加,同时移除log4j的依赖,看下打印的日志,会发现日志的输出和JUL的输出格式一模一样。

<dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

java日志分析器 java解析日志工具_java日志分析器_04


下面再把log4j的依赖打开,重新运行代码,发现日志的输出和log4j的格式一模一样。

java日志分析器 java解析日志工具_java_05


是不是可以验证前文提到的:JCL是一个抽象日志通用框架,本身不能直接记录日志,需要通过第三方来记录日志。那JCL的源码里面是怎么做的呢?下面来看一下。

java日志分析器 java解析日志工具_java_06

java日志分析器 java解析日志工具_spring_07

java日志分析器 java解析日志工具_slf4j_08


从源码中能理解了么?JCL会遍历一个数组,而这个数组里面依次放入的是Log4JLogger、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog也就是说如果项目中没有引入Log4J的依赖,就会使用JDK自带的JUL日志框架,与我们上面的测试结果一致。

4、Slf4j

下面引入slf4j的依赖,其他的依赖先不加,看下日志打印格式。

java日志分析器 java解析日志工具_spring_09


在其他什么依赖都不加的情况下,会打印上面的日志,是不是能说明Slf4J是一套简易Java日志门面,本身并无日志的实现,需要依赖第三方日志框架,它比JCL还狠,也不依赖Java自带的JUL

Slf4J是不是和JCL一样呢,只要把Log4J的依赖加入,就可以使用Log4J的日志框架了呢?答案是不是的。因为你想啊,Java自带的JUL它都没用到,怎么可能直接加入依赖就可以直接使用呢?

这里要引入Slf4J的绑定器的概念。具体可以看Slf4J的官网:

http://www.slf4j.org/manual.html。

如果我们需要让Slf4J使用log4j的日志框架,需要引入如下slf4j-log4j12绑定器依赖:

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25</version>
    </dependency>

java日志分析器 java解析日志工具_slf4j_10


如果需要使用Java自带的JUL日志框架,需要引入如下slf4j-jdk14绑定器依赖:

<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.25</version>
    </dependency>

java日志分析器 java解析日志工具_日志_11


这里需要注意一下的是,绑定器一般都会引入需要用到的日志框架,所以就无须额外引入需要的日志依赖。

好了,日志框架的具体使用都说过了,下面来说一个非常重要的知识点,那就是Slf4j的桥接器。

三、Spring日志框架

介绍完了各种日志框架,在介绍Slf4j桥接器之前,先来了解一下Spring是使用哪种日志技术。这地方要把Spring4Spring5分开讲,因为Spring5对日志框架做了调整。

1、Spring 4

Spring4 我们引入4.1.6.RELEASE这版本,我们先来看一下依赖关系,发现spring-core中依赖的日志框架是commons-logging ,也就是JCL

<dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>4.1.6.RELEASE</version>
      </dependency>

java日志分析器 java解析日志工具_slf4j_12


我们先不引入任何的日志框架,直接运行代码,发现日志结果的输出是和JUL的格式很像。

java日志分析器 java解析日志工具_java日志分析器_13


继续,我们引入log4j的依赖包,再运行程序,我们发现日志结果的输出变成log4j的格式了。

<dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

java日志分析器 java解析日志工具_spring_14


上面能说明什么问题呢?说明 Spring4 的日志框架用的就是上面介绍的JCL,再来看一下 Spring5

2、Spring 5

我们把Spring的依赖换成5.0.8.RELEASE这个版本(先别换再高版本),再来看一下依赖关系,发现日志框架变成了spring-jcl,这是神马东西,没听过额。这里要说一下,Spring5 使用的是 spring-jclspring5JCL基础上做了调整。

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>

java日志分析器 java解析日志工具_slf4j_15


同样的,我们先什么日志依赖不添加,直接运行程序,结果如下,发现日志结果格式和JUL一样。

java日志分析器 java解析日志工具_spring_16


那我们再加入Log4j的依赖看一下呢,同时把log4j的配置文件中的日志等级设置成error,不好意思,程序的运行结果和上面的一样。这能不能说明Spring5不支持log4j呢?答案是可以的。下面来看一下Spring5的源码,我们找到创建日志对象的这个地方,发现日志对象的创建依赖于logApi的值,那这个值什么时候赋值的呢?继续往下看。

java日志分析器 java解析日志工具_java日志分析器_17


发现LogApi有个默认值,默认值是JUL,而在静态代码块中,尝试创建的是Log4j 2.x,而不是Log4j。能看懂了么?

java日志分析器 java解析日志工具_spring_18


小结:从上面可知,Spring4使用的JCLSpring5使用的是spring-jcl,默认使用JCL去绑定JUL来打印日志,不支持log4j,如果要使用log4j,只能使用slf4j去绑定log4j好,下面要来说一下Spring5的高版本,本文以5.2.6.RELEASE为例子,直接运行程序,发现什么都打印不出来,这是为什么呢?看下源码。

java日志分析器 java解析日志工具_日志_19


java日志分析器 java解析日志工具_java日志分析器_20


同样的方法,看出来不同了嘛?在5.0.8版本中只要是info就可以被打印,但是在5.2.6版本中只有被trace或者debug的情况下才会被打印出来,这就是原因。

这里多说一下,如何在Spring5中使用log4j,只需要引入下面两个依赖,通过slf4j绑定log4j即可。

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.25</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25</version>
    </dependency>

四、Slf4J桥接器

上面介绍完了,下面终于要来说一下这个Slf4J桥接器了,先来看一下这张图。

java日志分析器 java解析日志工具_java日志分析器_21


来描述一个问题,如上图所示。我们知道Spring4的日志框架用的是JCL,我们添加log4j的依赖后,spring的日志输出用的是log4j的格式。而在我们的系统里引入了slf4j,并用绑定器绑定了JUL,我们系统的日志会用JUL打印出来,这样是不是会有问题?如果我们现在要求都用log4j的日志输出,该怎么办呢?下面来看需要统一日志的图,要求最后的日志输出格式为用JUL

java日志分析器 java解析日志工具_slf4j_22


先来看一下APP这条线路,slf4j----->slf4j-jdk14----->JUL----->Log

引入slf4j和绑定器slf4j-jdk14的依赖。并运行程序,看结果输出。

<dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.25</version>
        </dependency>
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-jdk14</artifactId>
          <version>1.7.25</version>
      </dependency>

java日志分析器 java解析日志工具_slf4j_23


再来看一下Spring这条线路,JCL----->log4j----->Log

引入JCLlog4j的依赖。运行程序,看下输出。

<dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

java日志分析器 java解析日志工具_java日志分析器_24

好了,上面的两段程序,所用的日志框架不一样,那现在怎么把它们整合成一种呢?这就需要用到Slf4j的桥接器。我们要把JCL桥接到Slf4j上,让Slf4j再使用JUL

引入jcl-over-slf4j依赖。

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.7.25</version>
    </dependency>

运行程序,看下Spring的输出结果,已经转换为用JUL

java日志分析器 java解析日志工具_spring_25

slf4j 桥接器地址:http://www.slf4j.org/legacy.html