Java日志体系整体梳理

一、Java日志体系的发展历程

  • Log4j:在JDK 1.3及以前,Java打日志依赖System.out.println(), System.err.println()或者e.printStackTrace(),Debug日志被写到STDOUT流,错误日志被写到STDERR流。这样打日志有一个非常大的缺陷,即无法定制化,且日志粒度不够细。
    于是,Ceki Gülcü 于2001年发布了Log4j,后来成为Apache 基金会的顶级项目。Log4j 在设计上非常优秀,对后续的 Java Log 框架有长久而深远的影响,它定义的Logger、Appender、Level等概念如今已经被广泛使用。Log4j 的短板在于性能,在Logback 和 Log4j2 出来之后,Log4j的使用也减少了。

  • JUL:Apache要求把log4j并入到JDK,SUN拒绝,并在jdk1.4版本后增加了JUL(java.util.logging),但是JUL功能远不如log4j完善,开发者需要自己编写Appenders(Sun称之为Handlers),且只有两个Handlers可用(Console和File),JUL在Java1.5以后性能和可用性才有所提升。

  • JCL(commons-logging):由于项目的日志打印必然选择两个框架中至少一个,如果有人想换成其他日志组件,如log4j换成JUL,因为api完全不同,就需要改动代码。Apache见此,开发了JCL(Jakarta Commons Logging)。JCL 是一个Log Facade,只提供 Log API,不提供实现,然后有 Adapter 来使用 Log4j 或者 JUL 作为Log Implementation。
    在程序中日志创建和记录都是用JCL中的接口,在真正运行时,会看当前ClassPath中有什么实现,如果有Log4j 就是用 Log4j, 如果啥都没有就是用 JDK 的 JUL。
    这样,在你的项目中,还有第三方的项目中,大家记录日志都使用 JCL 的接口,然后最终运行程序时,可以按照自己的需求(或者喜好)来选择使用合适的Log Implementation。如果用Log4j, 就添加 Log4j 的jar包进去,然后写一个 Log4j 的配置文件;如果喜欢用JUL,就只需要写个 JUL 的配置文件。如果有其他的新的日志库出现,也只需要它提供一个Adapter,运行的时候把这个日志库的 jar 包加进去。
    不过,commons-logging对Log4j和JUL的配置问题兼容的并不好,使用commons-loggings还可能会遇到类加载问题,导致NoClassDefFoundError的错误出现。到这个时候一切看起来都很简单,很美好。接口和实现做了良好的分离,在统一的JCL之下,不改变任何代码,就可以通过配置就换用功能更强大,或者性能更好的日志库实现。

  • SLF4J & Logback:这样看上去也挺美好的,但是log4j的作者觉得JCL不好用,自己开发出SLF4J(Simple Logging Facade for Java),它跟JCL类似,本身不替供日志具体实现,只对外提供接口或门面。目的就是为了替代JCL。同时,还开发出logback,一个比log4j拥有更高性能的组件,目的是为了替代log4j。
    SLF4J 是用来在log和代码层之间起到门面作用,类似于 JCL 的 Log Facade。对于用户来说只要使用SLF4J提供的接口,即可隐藏日志的具体实现,SLF4J提供的核心API是一些接口和一个LoggerFactory的工厂类,用户只需按照它提供的统一纪录日志接口,最终日志的格式、纪录级别、输出方式等可通过具体日志系统的配置来实现,因此可以灵活的切换日志系统。

  • log4j2:Apache参考了logback,并做了一系列优化,推出了log4j2

二、Java日志框架的关系和依赖

1、依赖关系

JCL支持日志组件不多(spring默认使用JCL输入日志),所以基本上没太多人用,现在主要以slf4j体系为主

image.png
image.png

以上两张图的意思是如果你想用slf4j作为日志门面的话,你如何去配合使用其他日志实现组件,这里说明一下(注意jar包名缺少了版本号,在找版本时也要注意版本之间是否兼容)

除了slf4j本身需要引入的slf4j-api.jar之外,其它还需要:

  • slf4j+logback: logback-classic.jar、 logback-core.jar
  • slf4j+log4j: slf44j-log4j12.jar、 log4j.jar
  • slf4j+jul: slf4j-jdk14.jar
  • slf4j+log4j2: log4j-slf4j-impl.jar、log4j-api.jar、log4j-core.jar

2、JCL桥接到slf4j

对于新项目来说,一般会使用logback+slf4j日志框架。那对于依赖的第三方(如spring框架)使用了log4j或者jul的怎么办,如何统一输出?答案是桥接器

比如:本项目使用的日志框架依赖是logback+slf4j日志框架

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.1.7</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-core</artifactId>
  <version>1.1.7</version>
</dependency>

可以添加如下的桥接器(将jul和log4j重定向到slf4j)

<!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.25</version>
</dependency>

具体的思路:
1、确定日志Facade,一般就是slf4j
2、确定日志实现框架,不同的日志框架针对slf4j都提供绑定器,也就是说slf4j可以配合大部分日志实现使用

  • slf4j-jdk14:slf4j到jdk-logging的桥梁
  • slf4j-log4j12:slf4j到log4j1的桥梁
  • log4j-slf4j-impl:slf4j到log4j2的桥梁这是apache实现,依赖的log4j版本较新。slf4j-log4j12这是slf4j中实现,依赖的log4j版本较低。实验可以互相替代,但是注意配置文件的不同。
  • logback-classic:slf4j到logback的桥梁
  • slf4j-jcl:slf4j到commons-logging的桥梁

3、管理依赖项目中的日志框架,拦截且重定向到slf4j中

  • jul-to-slf4j:jdk-logging到slf4j的桥梁
  • log4j-over-slf4j:log4j1到slf4j的桥梁
  • jcl-over-slf4j:commons-logging到slf4j的桥梁

3、循环引用问题

想象这样一种情况,通过桥接器将log4j的日志全部重定向到slf4j,然后slf4j又再次绑定到了log4j实现,死循环了。


image.png

三、Java日志框架各个jar包作用总结

1、各个jar包的总结

  • log4j1

    • log4j:log4j1的全部内容
  • log4j2

    • log4j-api:log4j2定义的API
    • log4j-core:log4j2上述API的实现
  • logback

    • logback-core:logback的核心包
    • logback-classic:logback实现了slf4j的API
  • commons-logging

    • commons-logging:commons-logging的原生全部内容
    • log4j-jcl:commons-logging到log4j2的桥梁
    • jcl-over-slf4j:commons-logging到slf4j的桥梁
  • slf4j转向某个实际的日志框架 (如:使用slf4j的API进行编程,底层想使用log4j1来进行实际的日志输出,这就是slf4j-log4j12干的事。)

    • slf4j-jdk14:slf4j到jdk-logging的桥梁
    • slf4j-log4j12:slf4j到log4j1的桥梁
    • log4j-slf4j-impl:slf4j到log4j2的桥梁
    • logback-classic:slf4j到logback的桥梁
    • slf4j-jcl:slf4j到commons-logging的桥梁
  • 某个实际的日志框架转向slf4j(如使用log4j1的API进行编程,但是想最终通过logback来进行输出,所以就需要先将log4j1的日志输出转交给slf4j来输出,slf4j再交给logback来输出。将log4j1的输出转给slf4j,这就是log4j-over-slf4j做的事)

    • jul-to-slf4j:jdk-logging到slf4j的桥梁
    • log4j-over-slf4j:log4j1到slf4j的桥梁
    • jcl-over-slf4j:commons-logging到slf4j的桥梁

2、集成总结

commons-logging与其他日志框架集成

  • commons-logging与jdk-logging集成,需要的jar包:
    • commons-logging
  • commons-logging与log4j1集成,需要的jar包:
    • commons-logging
    • log4j
  • commons-logging与log4j2集成,需要的jar包:
    • commons-logging
    • log4j-api
    • log4j-core
    • log4j-jcl(集成包)
  • commons-logging与logback,需要的jar包:
    • logback-core
    • logback-classic
    • slf4j-api、jcl-over-slf4j(2个集成包,可以不再需要commons-logging)
  • commons-logging与slf4j集成,需要的jar包:
    • jcl-over-slf4j(集成包,不再需要commons-logging)
    • slf4j-api

slf4j与其他日志框架集成

  • slf4j与jdk-logging集成,需要的jar包:
    • slf4j-api
    • slf4j-jdk14(集成包)
  • slf4j与log4j1集成,需要的jar包:
    • slf4j-api
    • log4j
    • slf4j-log4j12(集成包)
  • slf4j与log4j2集成,需要的jar包:
    • slf4j-api
    • log4j-api
    • log4j-core
    • log4j-slf4j-impl(集成包)
  • slf4j与logback集成,需要的jar包:
    • slf4j-api
    • logback-core
    • logback-classic(集成包)
      slf4j与commons-logging集成,需要的jar包:
    • slf4j-api
    • commons-logging
    • slf4j-jcl(集成包)

四、Java日志框架最佳实践

1、最佳实践建议:

  • 总是使用 Log Facade,而不是具体的 Log Implementation
  • 只添加一个 Log Implementation 依赖
  • 具体的日志依赖应该设置为 optional,并使用 runtime scope
    设为optional,依赖不会传递,这样如果你是个lib项目,然后别的项目使用了你这个lib,不会被引入不想要的Log Implementation 依赖;
    Scope设置为runtime,是为了防止开发人员在项目中直接使用Log Implementation中的类,而不使用Log Facade中的类。
  • 如果有必要, 排除依赖的第三方库中的Log Impementation依赖
    这是很常见的一个问题,第三方库的开发者未必会把具体的日志实现或者桥接器的依赖设置为optional,然后你的项目继承了这些依赖——具体的日志实现未必是你想使用的,比如他依赖了Log4j,你想使用Logback,这时就很尴尬。另外,如果不同的第三方依赖使用了不同的桥接器和Log实现,也极容易形成环。
    这种情况下,推荐的处理方法,是使用exclude来排除所有的这些Log实现和桥接器的依赖,只保留第三方库里面对Log Facade的依赖。

2、阿里Java日志规范

【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架
SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容