概述
本文的目的并不是详细介绍日志使用,而在于对现有主流日志系统的一个大致介绍,其目的是让我们更加合理的去使用日志,管理日志依赖关系。因为在开发过程中,我发现应用下面关于log的jar包非常的混乱,而这种混乱常常会带来jar包冲突、多份日志输出等问题,造成不必要的麻烦。比如你应用采用了log4j作为日志实现,但是你又通过间接依赖的方式引入了logback的包,这样开发者往往很难察觉,往往是出现了相应的异常现象才排查出log冲突的问题。
本文会先介绍现在主流日志框架,然后提出一个一些应用日志的规范,如果可行度很高,后续会给出更为严格的检测机制,帮助开发者去发现问题,防范于未然。
日志框架的历史
在Java开发过程中可用的日志工具非常之多,比如:
- Apache Commons Logging(Jakarta Commons Logging,JCL)
- Simple Logging Facade for Java (SLF4J)
- Apache Log4j(Log4j2)
- Java Logging API(JUL)
- Logback
- tinylog
在这些日志组件当中,最早得到广泛应用的是log4j,成为了Java日志的事实上的标准,现在可以看到很多应用都是依赖于log4j的日志实现。然而当时Sun公司在jdk1.4中增加了JUL(java.util.logging),企图对抗log4j,于是造成了混乱,当然此时也有其它的一些日志框架的出现,如simplelog等,简直是乱上加乱。
为了解决这种混乱Commons Logging出现了,他只提供日志的接口,而具体的实现则在运行过程中动态寻找。这样在代码中全部使用Commons Logging的编程接口,而具体日志实现则在外部配置中体现。
这样还有一个好处,由于应用日志并不依赖具体的实现,那么应用日志的实现则可以轻松的切换。所以现在也能看到很多应用基于Commons Logging+Log4j的搭配。
但是呢log4j的作者觉得Commons Loggin不够优秀(缺点在下文中介绍),于是自己实现了一套更为优雅的,这个就是SLF4J,并且还亲自实现了一个日志实现logback。那么现在关于log的局面就更为混乱了。为了让之前使用Commons Logging和JUL的能够很好的转到SLF4J的体系中来,log4j的作者又对其他的日志工具做了桥接......后来该作者又重写了log4j,这就是现在的log4j2,同时log4j2也加进了SLF4J体系中......(ps:这人确实牛逼!)
主流日志工具介绍
Commons-logging
Commons-logging是Apache提供的一个日志抽象,他提供一组通用的日志接口。应用自由选择第三方日志实现,像JUL、log4j等。这样的好处是代码依赖日志抽象接口,并不是具体的日志实现,这样在更换第三方库时带来了很大便利。
工作原理:
1、查找名为org.apache.commons.logging.Log的factory属性配置(可以是java代码配置,也可以是commons-logging.properties配置);
2、查找名为org.apache.commons.logging.Log的系统属性;
3、上述配置不存在则classpath下是否有Log4j日志系统,如有则使用相应的包装类;
3、如果系统运行在JDK 1.4系统上,则使用Jdk14Logger;
4、上述都没有则使用SimpleLog。
所以如果使用commons-logging+log4j的组合只需要在classpath中加入log4j.xml配置即可。commons-logging的动态查找过程是在程序运行时自动完成的。他使用ClassLoader来寻找和载入底层日志库,所以像OSGI这样的框架无法正常工作,因为OSGI的不同插件使用自己的ClassLoader。
SLF4J(Simple logging facade for Java)
SLF4J类似于commons-logging,他也是日志抽象。和commons-logging动态查找不同slf4j是静态绑定,他是在编译时就绑定真正的log实现。同时slf4j还提供桥接器可以将基于commons-loggging、jul的日志重定向到slf4j。比如程序中以前使用的commong-logging,那么你可以通过倒入jcl-over-slf4j包来讲日志重定向到slf4j。
SLF4J与各种日志实现的使用
SLF4J桥接
Log4j
log4j是Apache的开源日志框架,其最新版本是在2012年5月更新的1.2.17版本。他的2.0版本log4j2在其基础之上进行了重写,最新版本为2016年5月发布的2.6版本。log4j2插件式的架构、强大的配置功能、锁的优化、java8支持等特性都是非常吸引人的。如何升级请参考文档
Logback
Logback是由log4j创始人设计的又一个开源日志组件。当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本,此外logback-classic完整实现SLF4J API。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。Logback是要与SLF4J结合起来用。
最佳实现
二方库使用
二房库中建议不要绑定任何的日志实现,统一使用日志抽象(commons-logging、slf4j)。这里强烈建议使用slf4j。
包依赖如下
<!-- 除此之外不要依赖别的log包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
应用使用
新应用
slf4j+logback
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!-- logback-classic包含logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
slf4j+log4j
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!--slf4j-log4j12包含了log4j依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
老应用
老应用则没有改变日志的必要,因为会有开发成本。但是开发需要保证三点:
1、应用依赖中同一个log包不能出现多个版本;
2、日志实现框架必须唯一,可以log4j、logback等,但是不能出现既有log4j又有logback的情况;
3、日志桥接不要出现循环重定向,比如你加入了jcl-over-slf4j.jar之后又加入了slf4j-jcl.jar。
满足上述两点我感觉关于日志的问题应该会少很多。
检查机制
很多时候对于间接依赖开发者也不知晓应用存在多个日志实现包。所以采用有效的依赖检查机制很有必要。
想法
通过maven插件的形式,完成编译期依赖检查,通过开发者配置日志实现,检查应用中对日志的依赖。如发现多种日志实现包依赖则报错,这样来提醒开发者解决问题。