议题如下:
- 引言
- Java世界的log框架发展历程
- 什么是绑定实现(bingding)
- 什么是覆盖实现(override)
- 什么是桥接适配(bridge/route)
- 日志jar包之间的冲突
- Spring-boot为我们做了什么?
- Reference
引言
眼花缭乱的日志jar包,让我们无从下手
Java世界的log框架发展历程
- 先来认识一个人Ceki Gulcu
- https://github.com/ceki
- Java世界所有的log框架都跟他有关系
通过时间线整理如下
时间线 | 日志名称 | 作者 | 简介 |
2001年1月8日 | log4j1 | Ceki Gülcü/apache | 在jdk1.4之前广泛使用(是直接使用实现类,没有接口定义),并成为成为了Apache项目 |
2002年2月6日 | java.util.logging(jul) | sun | sun拒绝把apache的log4j纳入到jdk,并在jdk1.4版本之后新增了jul;没有接口api定义;java.util.logging.Logger; |
2002年8月13日 | commons-logging(jcl) | apache | 鉴于市面上log4j1和jul都广泛使用,但是两者之间的api完全不相同,无法快速切换。则抽象出一套log接口api定义,在运行时可以切换不同的日志实现组件。 目前已经停止更新了。 |
2006年 | slf4j | Ceki Gülcü | 虽然Ceki Gülcü认为抽象一套log接口的api定义是好的设计,但是他认为jcl的设计本身并不好用,于是自己开发出slf4j(Simple Logging Facade for Java)——同样也是不提供具体的log实现,只对外提供统一的接口/切面。目的就是要取代jcl |
2006年 | logback | Ceki Gülcü | Ceki Gülcü同时基于slf4j的抽象,也开发出logback作为log的实现,性能比log4j好。目的就是要取代log4j1 |
2014年7月 | log4j2 | apache | apache参考了logback的实现,推出了log4j2;log4j2自身是接口+实现 |
通过log历史进程,总结如下
- 两大日志接口:jcl(commons-logging),slf4j
- 目前一统江湖的就是apache的commons-logging和slf4j,他两的作用就是提供统一的接口,而具体的日志实现交给底层绑定的具体的日志实现框架。这样一来,我们的业务系统中可以灵活的更换不同的日志实现,并且可以不需要去改动代码。
- 对于开发者而言,每种日志都有不同的写法。如果我们以实现的日志框架来进行编写,代码就限制死了,之后就很难再更换日志系统,很难做到无缝切换。
- 抽象与实现分离。
什么是绑定实现(bingding)
JCL的绑定情况如下
接口名称 | 接口的jar | 实现名称 | 实现的jar | 实现的jar的含义 |
commons-logging(jcl) | commons-logging.jar | logging(jul) | jdk自带的日志实现 | |
动态绑定 | commons-logging.jar | log4j1(jcl亲儿子) | log4j.jar | log4j 一代版本就一个依赖jar,最高版本是12;log4j一代默认实现了jcl的api |
spring | commons-logging.jar | log4j2 | log4j-api.jar | 定义的api |
log4j-core.jar | api的实现 | |||
log4j-jcl.jar | 把log4j二代绑定到jcl;log4j二代默认实现的api是log4j-api.jar | |||
logback默认不支持 | 默认不支持 | logback是默认是基于slf4j实现 |
slf4j的绑定情况如下
接口名称 | 接口的jar | 实现名称 | 实现的jar | 实现的jar的含义 |
slf4j | slf4j-api.jar | logging(jul) | slf4j-jdk14.jar | slf4j绑定到jdk-logging;java.util.logging.Logger |
静态绑定 | slf4j-api.jar | log4j1 | slf4j-log4j12.jar | slf4j绑定到log4j |
log4j.jar | log4j 一代版本就一个依赖jar,最高版本是12 | |||
slf4j-api.jar | log4j2 | log4j-slf4j-impl.jar | slf4j绑定到log4j2二代; 注意,这个是apahce自己去实现的,并不是slf4j提供的 | |
log4j-api.jar | log4j 二代版本的api | |||
log4j-core.jar | log4j 二代版本的实现 | |||
slf4j-api.jar | logback(slf4j亲儿子) | logback-core.jar | logback的核心包 | |
logback-classic.jar | logback默认实现了slf4j的API | |||
slf4j-api.jar | jcl | slf4j-jcl.jar | slf4j绑定到jcl |
slf4j官网的介绍
- Slf4j厂商并没有开发支持log4j2的绑定代理jar;Apache开发了基于log4j2绑定代理jar:log4j-slf4j-impl
- 从maven仓库可以看到相关binding的字样
绑定实现的总结
jar包命名规则 | abc-xyz.jar:把abc绑定到xyz;abc作为接口存在,把实现绑定到xyz的实现上 |
使用场景 | 接口与实现之间的关系,就被当做一种绑定; 存量的项目中已经迁移到slf4j api上面来,但是发现一些场景必须使用其他的日志实现(jcl/log4j); 比如:配置文件不想修改,依赖的jar/项目里面直接使用了实现类,依赖的jar里面有配置文件等等; ————针对这种场景,需要的是一种绑定的概念,英文单词为bingding,把abc绑定到xyz; |
提供商 | 通常情况下,由该接口的厂商提供;目的在于让自己所出品的接口作为统一接口,但是可以让用户选择业界不同的日志实现框架 |
结论:接口是slf4j,实现则是其他日志框架(自己出品的接口,可以支持别人去实现) |
使用场景
源日志框架 | 需要依赖jar | 目标日志框架 |
jcl | jcl-over-slf4j.jar | slf4j |
log4j1 | log4j-over-slf4j.jar | slf4j |
覆盖实现的总结
jar包命名规则 | abc-over-xyz:abc的实现被xyz覆盖掉;abc作为实现,可以被xyz的实现所覆盖掉———提供了相同的包.类 |
使用场景 | 场景一,老项目的代码和配置是使用标准的api(接口+基础class),那么code+配置文件多是0修改。 场景二,新项目自身采用的是slf4j api日志框架,但是开发过程中依赖的组件(spring)/框架,其内部已经采用了某种日志框架,并且这些组件/框架内容使用了面向接口编程(但不是slf4j接口); |
提供商 | 由slf4j提供,致力于为不同的接口定义,提供slf4j的搞性能实现。 |
结论:接口不是slf4j,实现则是slf4j(别人的亲儿子,我也要替换。)老接口,新实现,旧实现尽量排除 |
扩展内容,细节介绍
Override 为了实现覆盖的效果:是通过提供同名的包 . 类的方式 —— 去替换原先的 jar 包里面的包 . 类。
所以理论上 override 的 jar 包,不可以与原先实现的 jar 包并存的,是要排除掉原先的 jar 包的
如果很难排除原先的 jar ,保证 override 的 jar 优先被加载,也是可以的。
•Slf4j的亲儿子logback的具体实现,是一定要依赖的。
如果项目里面直接使用具体的实现子类,那么 override 的 jar 包是失效的
通过 jar 的源码可以提高自身对 override的理解:打开log4j-1.2.17.jar与log4j-over-slf4j-1.7.30.jar你会发现他们包含了同名的包.类
使用场景
源日志框架 | 需要依赖jar | 目标日志框架 | 实现的jar的含义 |
jul | jul-to-slf4j | slf4j | 把jul的实现桥接到slf4j的实现上 (Jul是jdk自带的,没办法排除jar包,只能做桥接) |
log4j2 | log4j-to-slf4j | slf4j | 把log4j2的实现桥接到slf4j的实现上 (项目里面用了log4j2的api,Slf4j厂商并没有开发支持log4j2的override的jar包。Apache厂商就自己开发。) |
强调:选择“桥接适配”的方案,是只有当“ override ”方案不支持的时候,所选择的
不 到万不得已,不要选择该方案的 jar 包 —— 该方案会导致 log 的性能大幅度下降
jar 包命名规则 abc -to-xyz : abc 的实现被转发(桥接 / 路由 / 适配)到 xyz 的 实现
负责桥接的 jar 包与具体实现的 jar 包并存
官方对桥接适配的介绍
https://logging.apache.org/log4j/2.x/log4j-to-slf4j/index.html
A | B | 冲突原因 |
logback-classic.jar | log4j-slf4j-impl.jar | 两者都是针对slf4j的实现,运行时不能同时存在; |
log4j-to-slf4j.jar | log4j-slf4j-impl.jar | spring-boot的log框架校验机制。Spring-boot认为:在maven依赖上,如果依赖了log4j-to-slf4j.jar,则认定将log4j2的实现,交给slf4j了,那么就应该是走slf4j亲儿子去实现(logback);而依赖log4j-slf4j-impl.jar了意味着让log4j2作为slf4j的实现存在; |
jcl-over-slf4j.jar | slf4j-jcl | jcl-over-slf4j.jar意味着用slf4j所提供的实现去覆盖jcl的实现; slf4j-jcl意味着用jcl的实现slf4j接口; |
log4j-over-slf4j.jar | slf4j-log4j12 | 同上 |
jul-to-slf4j.jar | slf4j-jdk14 | 同上 |
spring-boot-starter-log4j2
用途:项目中所有的日志框架,可以转换成——以slf4j为接口,以log4j2为实现。
GroupId | ArtifactId | 简介 |
org.springframework.boot | spring-boot-starter-log4j2 | 默认不推荐,因此默认不传递依赖,需要项目手动依赖; 用于让项目采用面向slf4j接口api编程,但是采用log4j2实现的场景 |
spring-boot-starter-logging
用途:项目中所有的日志框架,可以转换成——以slf4j为接口,以logback为实现。
GroupId | ArtifactId | 简介 |
org.springframework.boot | spring-boot-starter-logging | 默认推荐,默认传递依赖;如果不想依赖,则需要手动排除 |
总结
但是 spring-boot 所提供的两个 log 依赖,都不包含 Override 的 jar 。
Override 的 jar 包是用来适配的场景是: 接口不是 slf4j ,但是实现层可以是 logback 。
所以, spring-boot 所提供的两个 log 依赖,仅仅是支撑 slf4j 作为 api 的场景所需要的。
• http ://www.slf4j.org/legacy.html
https:// logging.apache.org/log4j/2.x/log4j-to-slf4j/index.html