前言

对 java 日志一直不太明白,在项目中对日志这方面的关注也挺少的,但日志在项目中又很重要,所以找了很多资料,对日志有了一定的了解。

正文

1.日志框架介绍

jul:jul 是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架

Log4j: Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

LogBack: LogBack也是一个很成熟的日志框架,其实LogBack和Log4j出自一个人之手,这个人就是Ceki Gülcü。

  1. ogback当前分成三个模块:logback-core,logback- classic和logback-access。
  2. logback-core是其它两个模块的基础模块。
  3. logback-classic是Log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如Log4j或j.u.l。
  4. logback-access访问模块与Servlet容器集成提供通过Http来访问日记的功能。

Log4j2: 前面介绍过Log4j,这里要单独介绍一下Log4j2,之所以要单独拿出来说,而没有和Log4j放在一起介绍,是因为作者认为,Log4j2已经不仅仅是Log4j的一个升级版本了,而是从头到尾被重写的,这可以认为这其实就是完全不同的两个框架。

2.日志门面

在阿里开发手册中有做如下强制要求:

Java Thread里的日志不打印 java -jar 不打印日志_jar


这么多的日志框架为什么不可以直接使用呢?为什么要用slf4j呢?这里提到的门面模式又是什么呢?

门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。

Java Thread里的日志不打印 java -jar 不打印日志_适配层_02

为什么需要日志门面?

就像前面介绍的几种日志框架一样,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。

为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

在软件开发领域有这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。而门面模式就是对于这句话的典型实践。

3.常用的日志门面有哪些?

slf4j和commons-logging(也被称为JCL)。slf4j大家用的比较多,也成为日志门面事实上的标准。而commons-logging主要被spring等一些国外框架所使用。

4.SLF4J的原理

下面主要针对大家所熟悉的slf4j来聊一下门面做了哪些事儿以及如何做到的。

SLF4J共有两大特性。一是静态绑定、二是桥接。

我们先来看静态绑定

我们在coding时使用slf4j的api,而不需要关心底层使用的日志框架是什么。在部署时选择具体的日志框架进行绑定。

如下图,我们以log4j为例。首先我们的application中会使用slf4j的api进行日志记录。我们引入适配层的jar包slf4j-log412.jar及底层日志框架实现log4j.jar。简单的说适配层做的事情就是把slf4j的api转化成log4j的api。通过这样的方式来屏蔽底层框架实现细节。

Java Thread里的日志不打印 java -jar 不打印日志_jar_03


我们看一下这个图,上面的每一列都是讲如何把slf4j(日志门面)与其他日志的实现进行整合的过程。其中浅蓝色表示抽象的日志API,即我们说的日志门面(slf4j-api.jar),深蓝色是具体的日志实现,青色表示一个日志的适配器,灰色表示和slf4j没有直接关联的日志实现。

第一列:日志门面使用的是slf4j-api.jar,但是没有任何日志实现的话,那么日志讲不会输出任何内容。

第二列:说的是slf4j和logback如何绑定。日志门面使用的是slf4j-api.jar,那么日志的实现使用logback-classic.jar和logback-core.jar。我们之前讲过,logback是slf4j的亲儿子,所以slf4j可以和logback无缝连接。

第三列:说的是slf4j和log4j如何绑定,我们知道slf4j和log4j的作者是同一个人,但是作为接口出现的slf4j出现的要比log4j晚,所以slf4j和log4j并不能无缝连接,需要一个适配层slf4j-log4j12.jar。然后这个适配器实现了slf4j-api.jar中的接口和抽象类,但是是实现还是用的log4j的API去实现。由此解决了log4和slf4j整合的问题。

第四列:说的是slf4j和JUL(java.util.logging)如何绑定,原理同第三列。

第五列:slf4j本身也提供了一个简单实现,叫做slf4j-simple.jar。二者可以无缝连接。

第六列:slf4j本身也提供了一个没有任何实现的slf4j-nop.jar。这日志没有任何操作,不会输出日志。

看懂这个图,就了解了需要导入哪些jar文件的依赖了。每一个日志的实现框架都有自己的配置文件。但是以后的日志系统里面,这么多的日志实现,应该使用什么样的日志配置文件呢,我们有个原则就是:使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件。

再来看看桥接

其他日志框架统一转换为slf4j:

slf4j的另外一大特性就是桥接。比如你的application中使用了slf4j,并绑定了logback。但是项目中引入了一个A.jar,A.jar使用的日志框架是log4j.那么有没有方法让slf4j来接管这个A.jar包中使用log4j输出的日志呢?这就用到了桥接包。你只需要引入log4j-over-slf4j.jar并删除log4j.jar就可以实现slf4j对A.jar中log4j的接管.听起来有些不可思议。你可能会想如果删除log4j.jar那A.jar不会报编译错误嘛?答案是不会。因为log4j-over-slf4j.jar实现了log4j几乎所有public的api。但关键方法都被改写了。不再是简单的输出日志,而是将日志输出指令委托给slf4j。

Java Thread里的日志不打印 java -jar 不打印日志_java_04

第一块:

Java Thread里的日志不打印 java -jar 不打印日志_java_05


说的是一个系统中,如果要是用slf4j作统一的日志门面,并且用logback做日志的实现,但是此时还有commons-logging、log4j、jul等日志实现怎么办呢?此时需要用jcl-over-slfj.jar替代commons-logging,用log4j-over-slf4j.jar替代log4j.jar,并且添加jul-to-slf4j.jar(不能替换掉JUL,因为这是JDK自带的)

第二块:

Java Thread里的日志不打印 java -jar 不打印日志_适配层_06


说的是一个系统中,如果要是用slf4j作统一的日志门面,并且用log4j做日志的实现,但是此时还有commons-logging、jul等日志实现怎么办呢?此时需要用jcl-over-slfj.jar替代commons-logging,并且添加jul-to-slf4j.jar(不能替换掉JUL,因为这是JDK自带的),并且添加slf4j和log4j的适配层slf4j-log4j12.jar以及实现层log4j.jar。

第三块:

Java Thread里的日志不打印 java -jar 不打印日志_Java Thread里的日志不打印_07


说的是一个系统中,如果要是用slf4j作统一的日志门面,并且用JUL做日志的实现,但是此时还有commons-logging、log4j等日志实现怎么办呢?此时需要用jcl-over-slfj.jar替代commons-logging,用log4j-over-slf4j.jar替代log4j.jar,并且添加slf-jdk14.jar作为适配层,最后是用JDK自带的JUL作为日志的实现。

总结

  1. 应用中不应该直接使用具体日志框架中的api,而应该依赖slf4j的api。使用日志门面编程。具体的日志框架是在部署时确定的,表象上就是通过引用不同的jar包来实现slf4j到具体日志框架的绑定。常用的绑定有:
  • log4j:引入 slf4j-log412.jar和log4j.jar
  • logback:引入 logback-classic.jar和logback-core.jar
  1. 如何让系统中所有的日志都统一到slf4j?
  • 将系统中其他日志框架先排除出去;
  • 用中间包来替换原有的日志框架(根据上图);
  • 我们导入slf4j其他的实现

例如:
如果你的应用使用的slf4j+logback组合,但是引用的其他jar使用的是log4j等其他日志框架如何处理?

  • log4j:用log4j-over-slf4j.jar替换log4j.jar
  • jcl:用jcl-over-slf4j.jar替换commons-logging