一、引言
对于应用程序来说,日志的重要性不言而喻。而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
,看看打出的日志是神马样子。
2、log4j
下面引入log4j
的依赖,注意此处用的是1.X版本,我们知道,log4j
需要配置文件的,我们先不加,看看是什么效果。没错,报错了,这个错误是不是很熟悉。。。。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
不加配置文件的日志输出,有警告。
下面加下配置文件: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
从上面可以知道,JUL
和Log4j
是可以直接进行日志输出的。
3、JCL(Jakarta Commons Logging)
下面引入JCL
的依赖,其他的依赖先不加,同时移除log4j
的依赖,看下打印的日志,会发现日志的输出和JUL
的输出格式一模一样。
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
下面再把log4j
的依赖打开,重新运行代码,发现日志的输出和log4j
的格式一模一样。
是不是可以验证前文提到的:JCL
是一个抽象日志通用框架,本身不能直接记录日志,需要通过第三方来记录日志。那JCL
的源码里面是怎么做的呢?下面来看一下。
从源码中能理解了么?JCL
会遍历一个数组,而这个数组里面依次放入的是Log4JLogger、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog
。也就是说如果项目中没有引入Log4J
的依赖,就会使用JDK自带的JUL日志框架,与我们上面的测试结果一致。
4、Slf4j
下面引入slf4j的依赖,其他的依赖先不加,看下日志打印格式。
在其他什么依赖都不加的情况下,会打印上面的日志,是不是能说明Slf4J
是一套简易Java
日志门面,本身并无日志的实现,需要依赖第三方日志框架,它比JCL
还狠,也不依赖Java
自带的JUL
。
那Slf4J
是不是和JCL
一样呢,只要把Log4J
的依赖加入,就可以使用Log4J
的日志框架了呢?答案是不是的。因为你想啊,Java
自带的JUL
它都没用到,怎么可能直接加入依赖就可以直接使用呢?
这里要引入Slf4J的绑定器的概念。具体可以看Slf4J的官网:
如果我们需要让Slf4J
使用log4j
的日志框架,需要引入如下slf4j-log4j12
绑定器依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
如果需要使用Java自带的JUL日志框架,需要引入如下slf4j-jdk14
绑定器依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
这里需要注意一下的是,绑定器一般都会引入需要用到的日志框架,所以就无须额外引入需要的日志依赖。
好了,日志框架的具体使用都说过了,下面来说一个非常重要的知识点,那就是Slf4j的桥接器。
三、Spring日志框架
介绍完了各种日志框架,在介绍Slf4j
桥接器之前,先来了解一下Spring
是使用哪种日志技术。这地方要把Spring4
和Spring5
分开讲,因为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>
我们先不引入任何的日志框架,直接运行代码,发现日志结果的输出是和JUL
的格式很像。
继续,我们引入log4j
的依赖包,再运行程序,我们发现日志结果的输出变成log4j的格式了。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
上面能说明什么问题呢?说明 Spring4
的日志框架用的就是上面介绍的JCL
,再来看一下 Spring5
。
2、Spring 5
我们把Spring
的依赖换成5.0.8.RELEASE
这个版本(先别换再高版本),再来看一下依赖关系,发现日志框架变成了spring-jcl
,这是神马东西,没听过额。这里要说一下,Spring5
使用的是 spring-jcl
,spring5
在JCL
基础上做了调整。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
同样的,我们先什么日志依赖不添加,直接运行程序,结果如下,发现日志结果格式和JUL
一样。
那我们再加入Log4j
的依赖看一下呢,同时把log4j
的配置文件中的日志等级设置成error
,不好意思,程序的运行结果和上面的一样。这能不能说明Spring5
不支持log4j
呢?答案是可以的。下面来看一下Spring5
的源码,我们找到创建日志对象的这个地方,发现日志对象的创建依赖于logApi
的值,那这个值什么时候赋值的呢?继续往下看。
发现LogApi
有个默认值,默认值是JUL
,而在静态代码块中,尝试创建的是Log4j 2.x
,而不是Log4j
。能看懂了么?
小结:从上面可知,Spring4
使用的JCL
,Spring5
使用的是spring-jcl
,默认使用JCL
去绑定JUL
来打印日志,不支持log4j
,如果要使用log4j
,只能使用slf4j
去绑定log4j
。好,下面要来说一下Spring5
的高版本,本文以5.2.6.RELEASE
为例子,直接运行程序,发现什么都打印不出来,这是为什么呢?看下源码。
同样的方法,看出来不同了嘛?在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
桥接器了,先来看一下这张图。
来描述一个问题,如上图所示。我们知道Spring4
的日志框架用的是JCL
,我们添加log4j
的依赖后,spring
的日志输出用的是log4j
的格式。而在我们的系统里引入了slf4j
,并用绑定器绑定了JUL
,我们系统的日志会用JUL
打印出来,这样是不是会有问题?如果我们现在要求都用log4j
的日志输出,该怎么办呢?下面来看需要统一日志的图,要求最后的日志输出格式为用JUL
。
先来看一下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>
再来看一下Spring
这条线路,JCL----->log4j----->Log
引入JCL
和log4j
的依赖。运行程序,看下输出。
<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>
好了,上面的两段程序,所用的日志框架不一样,那现在怎么把它们整合成一种呢?这就需要用到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
了
slf4j 桥接器地址:http://www.slf4j.org/legacy.html