常用的日志组件
提问
:如果自己开发一个开源框架(如Spring),你将采用上述哪个日志组件?
发现哪个都不能采用,只能基于应用程序实际使用的日志组件来,不然就会日志打印多份。那具体怎么找到应用程序实际使用的日志组件呢?JCL(Apache Commons Logging)解决了这个问题。
在 sun 开发 logger 前,apache 项目已经开发了功能强大的 log4j 日志工具,并向 sun 推荐将其纳入到 jdk 的一部分,可是 sun 拒绝了 apache 的提议,sun 后来自己开发了一套记录日志的工具,即JUL。可是现在的开源项目都使用的是 log4j,log4j 已经成了事实上的标准,但由于又有一部分开发者在使用 sun logger,因此 apache 才推出 Apache Commons Logging,使得我们不必关注我们正在使用何种日志工具。
Apache Commons Logging(JCL)
之前叫Jakarta Commons Logging,简称JCL,是Apache提供的一个通用日志API,可以让应用程序不再依赖于具体的日志实现工具。Apache commons-logging是JCL的标准实现。
commons-logging包中对其它一些日志工具,包括Log4J、Avalon LogKit、JUL等,进行了简单的包装,可以让应用程序在运行时,直接将JCL API打点的日志适配到对应的日志实现工具中。
JCL通过动态查找的机制,在程序运行时自动找出实际使用的日志库。
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
SLF4J 全称 Simple Logging Facade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过Facade Pattern门面模式对外提供一些Java Logging API。这些对外提供的核心API其实就是一些接口以及一个LoggerFactory的工厂类。
在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定想用的Log库。
按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Apache Commons Logging。即使以后又出现更新的其他日志组件,也能完全适应。
SLF4J 静态绑定日志组件原理
使用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日志组件
只需要在pom.xml中加入:
<!-- 加入slf4j的核心API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j静态绑定log4j12的桥接包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
</dependency>
<!-- 实际的日志实现 log4j的依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
好了,到这里是不是觉得,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,好处是可以很方便的切换底层实现。
Java常用日志体系
实战: SpringMVC配置log4j