提问
:如果自己开发一个开源框架(如Spring),你将采用上述哪个日志组件?
发现哪个都不能采用,只能基于应用程序实际使用的日志组件来,不然就会日志打印多份。那具体怎么找到应用程序实际使用的日志组件呢?JCL(Apache Commons Logging)解决了这个问题。
在 sun 开发 logger 前,apache 项目已经开发了功能强大的 log4j 日志工具,并向 sun 推荐将其纳入到 jdk 的一部分,可是 sun 拒绝了 apache 的提议,sun 后来自己开发了一套记录日志的工具,即JUL。可是现在的开源项目都使用的是 log4j,log4j 已经成了事实上的标准,但由于又有一部分开发者在使用 sun logger,因此 apache 才推出 Apache Commons Logging,使得我们不必关注我们正在使用何种日志工具。
之前叫Jakarta Commons Logging,简称JCL,是Apache提供的一个通用日志API,可以让应用程序不再依赖于具体的日志实现工具。Apache commons-logging是JCL的标准实现。
commons-logging包中对其它一些日志工具,包括Log4J、Avalon LogKit、JUL等,进行了简单的包装,可以让应用程序在运行时,直接将JCL API打点的日志适配到对应的日志实现工具中。
JCL通过动态查找的机制,在程序运行时自动找出实际使用的日志库。
JCL为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根据指定顺序查找classPath是否存在相应日志的实现。如果JCL运行时没找到任何一种第三方的日志实现,则就用jdk14自带的java.util.logging(JUL)。假如你的maven工程pom.xml里加入了log4j的依赖,运行时JCL找到即可动态绑定。
Spring 的日志就是采用JCL,解决了应用程序和框架日志不统一的问题。动态去寻找(应用程序配置的)日志体系的实现。
但JCL方式也有不足,不能把所有日志的实现都包含。具体可以看commons-logging框架的LoggerFactory和LoggerFactoryImpl,里面硬编码了数组,不包含log4j2和logback的最新实现。
org.apache.commons.logging.impl.LogFactoryImpl见下图:
通过看JCL的源码,jcl为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根 据指定顺序查找classPath是否存在相应的实现,查找顺序就是数组元素的顺序。如果一个应用当中有多个classLoader。比如OSGI规定了每个模块都有其独立的ClassLoader。这种机制保证了插件互相独立,同时也限制了JCL在OSGi中的正常使用。这时出现了slf4j基于静态绑定的方式来解决了这个问题。
SLF4J 全称 Simple Logging Facade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过Facade Pattern门面模式对外提供一些Java Logging API。这些对外提供的核心API其实就是一些接口以及一个LoggerFactory的工厂类。
在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定想用的Log库。
按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Apache Commons Logging。即使以后又出现更新的其他日志组件,也能完全适应。
使用SLF4J时,如果你需要使用某一种日志实现,那么你选择相对应的SLF4J的桥接包即可。比如使用log4j日志组件,就选slf4j-log4j12桥接包,业务中就可以使用log4j进行底层日志输出。
SLF4J提供的桥接包:
• slfj-log4j12.jar (表示桥接 log4j)
• slf4j-jdk14.jar(表示桥接jdk Looging)
• sIf4j-jcl.jar(表示桥接 jcl)
• log4j-slf4j-impl(表示桥接log4j2)
• logback-classic(表示桥接 logback)
SLF4J提供了统一的记录日志的接口(LoggerFactory),只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
logback是slf4j-api的天然实现,不需要桥接包就可以使用。
与commons loging(JCL)不同的是其采用在classPath加入桥接jar包来表示具体采用哪种实现(静态绑定)
演示
:slf4j方式,使用log4j日志组件
org.slf4j
slf4j-api
1.7.25
org.slf4j
slf4j-log4j12
1.7.2
log4j
log4j
1.2.16
好了,到这里是不是觉得,Java日志体系目前有JCL和SLF4J的方式,是不是就万事大吉了?(其实并不是)
JCL方式的commons-logging 是动态查找绑定。
SLF4J是静态绑定,需要加桥接包。如slf4j-log4j2。
需求
:我的应用程序想使用log4j2打印日志;而Spring采用的JCL中不包含log4j2(LoggerFactoryImpl源码中定义的数组中不包含log4j2),运行时,JCL从数组顺序寻找日志的实现,如果没有引入其他实现,最终则会用JUL打印日志,如下图。
这时会出现什么问题呢?Spring 打印日志方式和应用程序打印日志不统一,错误排除时比较困难。而且应用程序和Spring框架,日志不统一,太乱了,还得搞2份日志配置文件。
为了让Spring和我们的应用程序,采用统一的log4j2 日志体系,需要加入适配器。
改善上面应用程序和框架日志不统一的问题(加入适配器后):
中间这个适配器:其实就是jcl-over-slfj.jar(jcl适配slf4j),加入下面的依赖即可,再去除commons-logging。
最后SLF4J的总结:无论你选用哪个日志组件,在你的应用中,都应当使用slf4j作为统一的API,好处是可以很方便的切换底层实现。
实战: SpringMVC配置log4j