log.info("xxx")
这种形式,为什么在我们的 pom.xml
中要配置那么多的日志依赖,或者有时还需要主动将某些第三方依赖里面的日志依赖给主动去掉,相信读完本篇文章,你对这些问题的疑惑将一一解除。
生活中的日志是记录你生活的点点滴滴,让它把你内心的世界表露出来,更好的诠释自己的内心世界,而电脑里的日志可以是有价值的信息宝库,也可以是毫无价值的数据泥潭。
以上的介绍你百度百科对日志
这个概念的介绍,想必阅读本篇文章的人都对日志是有一定的了解的了,所以本文也不做过多的赘述,我们直接进入后面对日志框架以及门面的介绍
在Java领域,常见的日志框架有JUL、Log4j、Logback、log4j2
log4j可以当之无愧地说是Java日志框架的元老,1999年发布首个版本,2012年发布最后一个版本,2015年正式宣布终止,至今还有无数的系统在使用log4j,甚至很多新系统的日志框架选型仍在选择log4j。
然而老的不等于好的,在IT技术层面更是如此。尽管log4j有着出色的历史战绩,但早已不是Java日志框架的最优选择。
在log4j被Apache Foundation收入门下之后,由于理念不合,log4j的作者Ceki离开并开发了slf4j和logback。
slf4j因其优秀的性能和理念很快受到了广泛欢迎,2016年的统计显示,github上的热门Java项目中,slf4j是使用率第二名的类库(第一名是junit)。
logback则吸取了log4j的经验,实现了很多强大的新功能,再加上它和slf4j能够无缝集成,也受到了欢迎。
在这期间,Apache Logging则一直在关门憋大招,log4j2在beta版鼓捣了几年,终于在2014年发布了GA版,不仅吸收了logback的先进功能,更通过优秀的锁机制、LMAX Disruptor、"无垃圾"机制等先进特性,在性能上全面超越了log4j和logback。
好了。那么上面我们讲完了框架们的历史,下面我们正式对这些日志框架进行基本的介绍
j.u.l是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架。Java Logging API提供了七个日志级别用来控制输出。这七个级别分别是:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST。
Log4j 是一种非常流行的日志框架,由Ceki Gülcü
首创,之后将其开源贡献给 Apache 软件基金会。
通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
Log4j也有七种日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG和TRACE。
log4j可以说是当之无愧的Java日志框架的元老,1999年发布首个版本,2012年发布最后一个版本,2015年正式宣布终止,至今还有无数的系统在使用log4j,甚至很多新系统的日志框架选型仍在选择log4j。
在log4j被Apache Foundation收入门下之后,由于理念不合,log4j的作者Ceki离开并开发了slf4j和logback。
Logback也是一个很成熟的日志框架,从06年开始第一个版本,迭代至今也十几年了。其中Logback、Log4j、Slf4j都是出自同一个人之手
logback 主要分为3个模块:
前面介绍过Log4j,这里要单独介绍一下Log4j2,之所以要单独拿出来说,而没有和Log4j放在一起介绍,是因为作者认为,Log4j2已经不仅仅是Log4j的一个升级版本了,而是从头到尾被重写的,log4j2 是 log4j 1.x 的升级版,其参考了 logback 的一些优秀的设计,并且修复了一些问题,其实可以认为这其实就是完全不同的两个框架。
在现在我们单独使用时:其jar包分别是
上面我们并没有对这些日志框架进行非常详细的使用介绍,感兴趣的同学们可以自行查询相关资料进一步了解,这里我们仅提供这些日志框架的对比信息
在以上介绍的日志框架中,logback和log4j2都宣称自己是log4j的后代,一个是出于同一个作者,另一个则是在名字上根正苗红。由于Log4j Apache已经不再更新,所以我们现在使用的日志框架,基本就是在logback以及log4j2之中选择一个。
撇开血统不谈,我们比较一下log4j2和logback:
关于它们两者的性能对比,有兴趣的大家可以参阅这篇文章
前面介绍了四种日志框架,也就是说,我们想要在应用中打印日志的时候,可以使用以上四种类库中的任意一种。比如想要使用Log4j,那么只要依赖Log4j的jar包,配置好配置文件并且在代码中使用其API打印日志就可以了。
不知道有多少人看过《阿里巴巴Java开发手册》,其中有一条规范做了『强制』要求:
说好了以上四种常用的日志框架是给Java应用提供的方便进行记录日志的,那为什么又不让在应用中直接使用其API呢?这里面推崇使用的SLF4J是什么呢?所谓的门面模式又是什么东西呢?
日志门面,是门面模式的一个典型的应用。
门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用,这里我们也可以抽象地理解为Java的接口。
就像前面介绍的几种日志框架一样,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。
为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。
在软件开发领域有这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。而门面模式就是对于这句话的典型实践。
前面提到过一个重要的原因,就是为了在应用中屏蔽掉底层日志框架的具体实现。这样的话,即使有一天要更换代码的日志框架,只需要修改jar包,最多再改改日志输出相关的配置文件就可以了。这就是解除了应用和日志框架之间的耦合。
有人或许会问了,如果我换了日志框架了,应用是不需要改了,那日志门面不还是需要改的吗?
要回答这个问题,我们先来举一个例子,再把门面模式揉碎了重新解释一遍。
日志门面就像饭店的服务员,而日志框架就像是后厨的厨师。对于顾客这个应用来说,我到饭店点菜,我只需要告诉服务员我要一盘番茄炒蛋即可,我不关心后厨的所有事情。因为虽然主厨从把这道菜称之为『番茄炒蛋』A厨师换成了把这道菜称之为『西红柿炒鸡蛋』的B厨师。但是,顾客不需要关心,他只要下达『番茄炒蛋』的命令给到服务员,由服务员再去翻译给厨师就可以了。
所以,对于一个了解了”番茄炒蛋的多种叫法”的服务员来说,无论后厨如何换厨师,他都能准确的帮用户下单。
同理,对于一个设计的全面、完善的日志门面来说,他也应该是天然就兼容了多种日志框架的。所以,底层框架的更换,日志门面几乎不需要改动。
以上,就是日志门面的一个比较重要的好处——解耦。
介绍过了日志门面的概念和好处之后,我们看看Java生态体系中有哪些好的日志门面的实现可供选择。
Java简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有Java Logging API、Log4j及logback等框架。以MIT 授权方式发布。其作者是 Log4j 以及 Logback 的作者 Ceki Gülcü
。
其实,SLF4J只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖其背后的日志实现Log4j2、logback等日志框架的。
Apache Commons Logging(原名 Jakarta Commons Logging,JCL)是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供API,日志实现和包装器实现。
commons-logging和SLF4J的功能是类似的,主要是用来做日志 门面的。提供更加好友的API工具。
这里我们用Slf4j为例,通过Slf4j可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在它编译时需要静态绑定真正的Log库。使用Slf4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。
这个东西要用图才能说得清楚,借用官网一张图:
包名 | 桥接流 | 功能 |
---|---|---|
jcl-over-slf4j.jar | jcl -> slf4j | Jakarta Commons Logging日志框架到 slf4j 的桥接 |
jul-to-slf4j.jar | juc -> slf4j | java.util.logging的日志桥接到 slf4j |
log4j-over-slf4j.jar | log4j -> slf4j | 将log4j 的日志,桥接到slf4j |
osgi-over-slf4j.jar | osgi -> slf4j | 将osgi环境下的日志,桥接到slf4j |
slf4j-api.jar | slf4j 的api接口jar包 | |
slf4j-jcl.jar | lf4j -> jcl | slf4j 转接到 Jakarta Commons Logging日志输出框架 |
slf4j-jdk14.jar | slf4j -> jul | slf4j 转接到 java.util.logging,注意这个包不能和jul-to-slf4j.jar同时用,否则会死循环! |
slf4j-log4j12.jar | slf4j -> log4j | slf4j 转接到 log4j,注意这个包不能和log4j-over-slf4j.jar同时用,否则会死循环! |
slf4j-nop.jar | slf4j -> null | slf4j的空接口输出绑定,丢弃所有日志输出 |
slf4j-simple.jar | slf4j -> slf4j-simple | slf4j自带的简单日志输出接口 |
其实,在现在的日志框架中,有些框架本身也有针对其他的日志框架做一部分桥接的功能,如Log4j2
包名 | 桥接流 | 功能 |
---|---|---|
log4j-1.2-api.jar | log4j -> log4j2 | 将log4j 的日志转接到log4j2日志框架,注意这里的1.2 不是版本号,而是1-to-2的意思 |
log4j-api.jar | log4j2的api接口jar包 | |
log4j-core.jar | log4j2的日志输出核心jar包 | |
log4j-slf4j-impl.jar | slf4j -> log4j2 | slf4j 转接到 log4j2 的日志输出框架 |
log4j-to-slf4j.jar | log4j2 -> slf4j | 将 log4j2的日志桥接到 slf4j |
log4j-jul | jul -> log4j2 | 将jul的日志桥接到log4j2 |
从这里就已经可以看到,日志框架之间的关系有点乱。
因为log4j2和slf4j都能对接多种日志框架,所以这些包的依赖,作用,还有命名,都容易让人混淆。
PS:Log4j第一代的包名就叫
Log4j
,其最新的版本为2012年更新的1.2.17
,后续的各种log4j-xxx
,其实大部分都属于Log4j2
的包了
下面我们可以演示下各种日志类型的桥接包应该如何引入
那么就得去掉 log4j 1.x jar,添加log4j-1.2-api.jar,配合 log4j-api-2.x.x.jar 和 log4j-core-2.x.x.jar 即可,依赖如下
log4j-1.2-api.jar
log4j-api-2.x.x.jar
log4j-core-2.x.x.jar
这里我们只是演示如何做桥接,实际上当然不建议这么做的。
理清了上面的jar包作用,就会发现,可以通过 log4j2 -> slf4j -> log4j 的方式来实现。
需要的jar包,根据依赖关系分别是:
log4j-api-2.x.x.jar
log4j-to-slf4j.jar
slf4j-api-x.x.x.jar
slf4j-log4j12-x.x.x.jar
log4j-1.2.17.jar
将代码中的log4j日志桥接到 slf4j,需要如下jar包
log4j-over-slf4j-x.x.x.jar
slf4j-api-x.x.x.jar
将代码中的log4j2日志桥接到 slf4j,需要如下jar包
log4j-api-2.x.x.jar
log4j-to-slf4j-2.x.x.jar
slf4j-api-x.x.x.jar
slf4j -> log4j2
将slf4j日志,采用log4j2实现进行输出,需要如下jar包
slf4j-api-x.x.x.jar
log4j-slf4j-impl.jar
log4j-api.jar
log4j-core.jar
通常在我们的spring-boot-starter依赖中,会默认引入日志依赖如下:
我们通过查看Spring默认的日志依赖引入,可以清楚的看到,其使用日志门面slf4j
来适配底层的日志框架,而日志框架用的则是logback
,同时提供了对Java默认的JUL转换的SLF4J的适配器。
细心的朋友们可以看到,这里spring还默认引入了一个log4j-to-slf4j
的适配器,在我们主动将该配置Exclude之后其并不会影响我们项目的运行。
PS:这里的
log4j-to-slf4j
大家不要以为是Log4j一代哈,根据上文日志桥接章节所说,它其实是Log4j2,只是官方省写了2,其作用是将 log4j2的日志桥接到 slf4j
那么为什么Spring默认要引入这个配置呢?
这其实是为了提高我们代码依赖的鲁棒性,现如今,很多的组件/框架都会使用日志进行日志打印,而我们无法保证他们用的一定是logback/log4j2,因此,spring通过默认引入log4j2的桥接包,可以在我们引入使用log4j2作为日志框架的第三方组件时,自动将其桥接到slf4j,进而使用我们指定的日志框架(如spring默认的logback)实现进行打印
针对这个问题,大家可以查看该链接进行了解
如果我们不想使用Spring默认的日志框架,那么也可以采用类似的方式将它们对应的 spring-boot-starter 依赖模块加到 Maven 依赖中即可,如Log4j2使用如下依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
但一定不要将这些完成同一目的的 spring-boot-starter 都加到依赖中(这就是我们开头讲到的为什么有些日志依赖需要我们主动排除掉)
根据不同的日志系统,可以按如下规则组织配置文件名,就能被Spring正确加载:
Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项
如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以通过logging.config属性指定自定义的名字:
logging.config=classpath:logging-config.xml
在Java生态体系中,围绕着日志,有很多成熟的解决方案。关于日志输出,主要有两类工具。
一类是日志框架,主要用来进行日志的输出的,比如输出到哪个文件,日志格式如何等。 另外一类是日志门面,主要一套通用的API,用来屏蔽各个日志框架之间的差异的。
所以,对于Java工程师来说,关于日志工具的使用,最佳实践就是在应用中使用如Log4j2/Logback + SLF4J 这样的组合来进行日志输出。
这样做的最大好处,就是业务层的开发不需要关心底层日志框架的实现及细节,在编码的时候也不需要考虑日后更换框架所带来的成本。这也是门面模式所带来的好处。
综上,请不要在你的Java代码中出现任何Log4j等日志框架的API的使用,而是应该直接使用SLF4J这种日志门面。
文章参考链接:
https://www.jianshu.com/p/85d141365d39
https://www.hollischuang.com/archives/3000
https://www.jianshu.com/p/d7b0e981868d
https://www.springboottutorial.com/logging-with-spring-boot-logback-slf4j-and-log4j