java日志体系

1、Java 日志体系发展历程

在实际开发过程中,Java的日志框架体系之所以会给编程人员带来一种混乱的感觉,根本原因是Java日志框架没有统一的使用标准。

image.png

在最初的上古时代,程序员们并没有现成的日志框架,Java开发者们只能使用原始的System.out.println(), System.err.println()或者e.printStackTrace()。通过把debug日志写到StdOut流,错误日志写到ErrOut流,来实现应用程序运行状态的记录。这样有一个很大的问题,那就是无法定制化,并且日志的输出粒度不够细。

于是1999年,Ceki Gülcü创建了Log4j项目,并几乎成为了Java日志框架的实际标准。

Log4j作为Apache基金会的一员,Apache希望将Log4j引入jdk,不过被sun公司拒绝了。随后,sun模仿Log4j,在jdk1.4中实现了JUL即java.util.logging。

后来为了实现将日志接口与实现解耦,Apache推出了JCL即Apache Commons Logging。JCL只定义了一套日志接口,具体实现由Log4j或JUL来完成。JCL基于动态绑定来实现日志的记录,在使用时只需要用JCL定义的接口来编写代码即可,程序真正运行时会检查classpath中的具体实现,因此可以自由选择是有log4j还是JUL来实现日志功能。

Slf4j&Logback,后来,Java日志大佬Ceki Gülcü离开了Apache自己单干,先后创建了Slf4j和Logback两个项目。Slf4j是一个日志门面,只提供接口,可以支持Logback、JUL、log4j等实现;Logback作为大佬自己提供的具体实现,相较于log4j有更快的执行速度和更完善的功能。

log4j2,最后,Apache为了避免Slf4j和Logback,慢慢取代JCL和Log4j ,推出了自己的反击之作log4j2。log4j2不兼容log4j,性能获得了很大的提升。于是,我们就看到了如此多的日志框架并存的局面。

image.png

2、日志门面、桥接器与日志实现

2.1、门面模式

比直接使用具体日志框架更合理的选择是使用日志门面接口。日志门面接口提供了一套独立于具体日志框架实现的API,应用程序通过使用这些独立的API就能够实现与具体日志框架的解耦,这跟JDBC是类似的。最早的日志门面接口是commons-logging,但目前最受欢迎的是SLF4J

SLF4JSimple logging Facade for Java

意思为简单日志门面,它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接口方法进行调用即可,由于它只是一个接口,并不是一个具体的可以直接单独使用的日志框架,所以最终日志的格式、记录级别、输出方式等都要通过接口绑定的具体的日志系统来实现,这些具体的日志系统就有log4j2log4jlogbackjava.util.logging等,它们才实现了具体的日志系统的功能。日志门面在使用时,可以动态或者静态的指定具体的日志框架实现,实现了接口和实现的解耦,使得用户可以灵活的选择日志的具体实现框架。

2.2、sl4j与具体日志框架的结合

Java日志体系当前大体可以分为三个部分:日志门面接口、桥接器、日志框架具体实现。门面接口与日志实现上面已经介绍过了。那么什么是桥接器呢?所谓“桥接器”,不过就是对某套API的伪实现,这种实现并不是直接去完成API所声明的功能,而是去调用有类似功能的别的API。这样就完成了从“某套API”到“别的API”的转调。如果同时存在A-to-B.jar和B-to-A.jar这两个桥接器,那么可以想象当应用程序开始调用A或者B的API时,会发生什么事。这就是最开始引出的那个stack overflow异常的基本原理。

下图来自SL4J官网文档。

image.png

可以看到slf4j与具体日志框架结合的方案有很多种。当然,每种方案的最上层(绿色的应用层)都是统一的,它们向下都是直接调用slf4j提供的API(浅蓝色的抽象API层),依赖slf4j-api.jar。然后slf4j API向下再怎么做就非常自由了,几乎可以使用所有的具体日志框架。注意图中的第二层是浅蓝色的,看左下角的图例可知这代表抽象日志API,也就是说它们不是具体实现。如果像左边第一种方案那样下层没有跟任何具体日志框架实现相结合,那么日志是无法输出来的( 这里不确定是否可能会默认输出到标准输出 )。

图中第三层明显就不如第一、二层那么整齐划一了,因为这里已经开始涉及到了具体的日志框架。

首先看第三层中间的两个湖蓝色块,这是适配层,也就是桥接器。左边的slf4j-log4j12.jar桥接器看名字就知道是slf4j到log4j的桥接器,同样,右边的slf4j-jdk14.jar就是slf4j到Java原生日志实现的桥接器了。它们的下一层分别是对应的日志框架实现,log4j的实现代码是log4j.jar,而jul实现代码已经包含在了JVM runtime中,不需要单独的jar包。

再看第三层其余的三个深蓝色块。它们三个也是具体的日志框架实现,但是却不需要桥接器,因为它们本身就已经直接实现了slf4j API。slf4j-simple.jar和slf4j-nop.jar这两个不用多说,看名字就知道一个是slf4j的简单实现,一个是slf4j的空实现,平时用处也不大。而logback之所以也实现了slf4j API,是因为logback和slf4j出自同一人之手。

第三层所有的灰色jar包都带有红框,这表示它们都直接实现了slf4j API,只是湖蓝色的桥接器对slf4j API的实现并不是直接输出日志,而是转去调用别的日志框架声明的API。

2.3、其他日志框架桥接到sl4j

如果只存在上面这些从sfl4j到其他日志框架的桥接器,可能还不会出什么问题。但是实际上还有另外一类桥接器,它们的作用跟上面的恰好相反,它们将其它日志框架的API转调到slf4j的API上。

下图来自SL4J官网文档。

image.png

上图展示了目前为止能安全地从别的日志框架API转调回slf4j的所有三种情形。

以左上角第一种情形为例,当slf4j底层使用的具体实现是logback时,上层允许桥接到slf4j的日志框架API有log4j和jul。jcl虽然不是什么日志框架的具体实现,但是它的API仍然是能够被转调回slf4j的。要想实现转调,方法就是图上列出的用特定的桥接器jar替换掉原有的日志框架jar。

看完三种情形以后,会发现几乎所有其他日志框架的API,包括jcl的API,都能够随意的转调回slf4j。 但是有一个唯一的限制就是转调回slf4j的日志框架不能跟slf4j当前使用的底层具体日志实现框架相同。 这个限制就是为了防止A-to-B.jar跟B-to-A.jar同时出现在类路径中,从而导致A和B一直不停地互相递归调用,最后堆栈溢出。目前这个限制并不是通过技术保证的,仅仅靠开发者自己保证,这也是为什么slf4j官网上要强调所有合理的方式只有上图的三种情形。

假设slf4j+log4j:一个具体的日志操作的过程,首先调用 slf4j 的 API 接口,然后 slf4j 将请求转发给 slf4j-log4j12 来处理,这个是在编译期间静态绑定好的。然后 slf4j-log4j12 最后通过 log4j 来完成日志操作。
如果再加一个 log4j-over-slf4j会怎样?其实日志请求又重新打回给 slf4j,形成一个环形

image.png

到这里,本文一开始所展示的那个异常的原理基本已经清楚了。此外,通过上图还可以看出可能会出现类似异常的组合不仅仅是log4j-over-slf4j和slf4j-log4j12,slf4j官网还指出了另外一对: jcl-over-slf4j.jar和slf4j-jcl.jar

2.4、multiple SLF4J bindings警告

其实本文一开始的启动异常是可以提前避免的,使用maven编译工程时可以发现如下multiple SLF4J bindings警告,如果直接忽略此警告,启动spring boot应用就会报上面提到的stackOverFlow异常。

image.png

上边警告大概意思是说logback-classic包和log4j-slf4j-impl包,关于org/slf4j/impl/StaticLoggerBinder.class 这个类发生了冲突。原因是一个接口,被两个实现类实现了,然则程序在启动获取时,只想获取一个。这个接口是:org.apache.logging.slf4j.Log4jLoggerFactory
两个实现类分别是:

logback-classic包中的org.slf4j.impl.StaticLoggerBinder
log4j-slf4j-impl包org.slf4j.impl.StaticLoggerBinder
发生这个错误的原因,首先logback日志的开发者和log4j 的开发是同一个人,而spring boot 默认日志是较新的logback 日志。但是在以前流行的日志却是log4j ,而且很多的第三方工具都含有log4j的引入。而我们在项目开发中,难免会引入各种各样的工具包,所以,基本上springboot 项目,如果不注意,肯定会出现这种冲突的。

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

推荐阅读更多精彩内容