Java日志概述

今天看到了一篇对Java日志系统讲解很不错的文章,所以做个学习记录,如有侵权请联系删除

概述

       Java的日志系统非常丰富,常用的有log4j、JUL、logback等等,随着日志系统的发展出现了日志框架commons-logging、slf4j

发展史

       日志最早出现的是apache开源社区的log4j,是应用最为广泛的日志工具,然而sun公司在JDK1.4中增加了JUL日志实现企图对抗log4j,同时断断续续也出现了其他的日志工具,这就造成了混乱,因为这些日志系统互相没有关联,替换和统一变得棘手,想象一下你的应用使用了log4j,然后使用了其他团队的库,而他们使用了JUL,那么你的应用就需要使用两个日志系统了,然后又有第二个库使用了simplelog,这个时候估计你就会崩溃了...那么如何解决呢?进行抽象,抽象出一个接口层对每个日志实现都适配或者转接,提供给别人的库都直接使用抽象层而具体的实现由使用者决定。不错,开源社区提供了commons-logging抽象,被称为JCL,JCL确实出色的完成了兼容主流的日志实现(log4j、JUL、simplelog),基本一统江湖,就连大名鼎鼎的spring也是依赖了JCL。然而好景不长,另一个优秀的日志框架slf4j的出现使场面更加混乱,而slf4j的作者(Ceki Gülcü)正是log4j的作者,他觉得JCL不够优秀所以要搞一套更优雅的出来,于是slf4j诞生了,同时为slf4j实现了一个亲儿子——logback。
       slf4j确实更加优雅,但是之前已有很多代码库已经使用了JCL,虽然出现了slf4j与JCL之间的桥接转换,但是集成的时候依然问题多多,到此本来应该完了,但是Ceki Gülcü觉得还是得回头拯救下自己的“大阿哥”——log4j,于是log4j2诞生了,同样log4j2也参与到了slf4j日志体系中,想必将来会更加混乱......

JCL

       使用JCL一般需要配置一个commons-logging.properties在classpath上,这个文件有一行代码:

org.apache.commons.logging.LogFactory= org.apache.commons.logging.impl.LogFactoryImpl

       这个是告诉JCL我们要使用哪个日志实现,JCL会在classpath下去加载对应的日志工厂实现类,具体的日志工厂实现类可以是log4j,也可以是jul等等。用户主需要依赖JCL的api即可,对日志系统的替换主需要修改一下commons-logging.properties文件切换到对应的日志工厂实现即可,但是我们也可以看到因为JCL是在运行时去加载classpath下的实现类,会有classloader的问题。

slf4j

       slf4j的设计确实比较优雅,它采用了我们比较熟悉的方式——接口和实现分离,有个纯粹的接口层slf4j-api工程,这个里面基本完全定义了日志的接口,所以对于开发者来说只需要是这个即可。
       有接口就要有实现,比较推崇的实现是logback,logback完全实现了slf4j-api的接口,并且性能是那个也比log4j更好,我们知道log4j的使用比较普遍,所以为了支持这部分用户是必须的,slf4j-log4j12也实现了slf4j-api,这个算是对log4j的适配器。同样的道理,对JUL的是配置为slf4j-jdk14。
       为了让使用JCL等等其他其他日志系统的用户可以很简单的切换到slf4j上来,给出了各种桥接工程,例如:jcl-over-slf4j会把JCL的调用都桥接到slf4j上来(可以看出jcl-over-slf4j的api和JCL是相同的,所以这两个jar是不能共存的),jul-to-slf4j是把jul的调用桥接到slf4j上,log4j-over-slf4j是把log4j的调用桥接到slf4j,下面用一张图来表示下这个家族的大致成员(红线表示冲突)


       如上图所示,最上层表示桥接层,中间是接口层,最下层表示具体的实现,可以看出这个图中所有的jar都是围绕着slf4j活动的,其中slf4j-jul的jar包名称是slf4j-jdk14
       slf4j-api和具体的实现层是怎么绑定的呢?这个其实是在编译时绑定的,它可以不需要像使用JCL那样需要配置一下,只需要把slf4j-api和slf4j-log4j放到classpath上,即实现绑定。原理可以下载slf4j-api的源码查看,这个设计还是很巧妙的,slf4j-api中会去调用StaticLoggerBinder这个类获取绑定的工厂类,而每个日志实现会在自己的jar中提供这样一个类,这样slf4j-api就实现了编译时绑定实现。但是这样接口的源码编译需要依赖具体的实现了,不太合理吧?这里容易让人迷惑,因为打开slf4j-api的jar,看不到StaticLoggerBinder,当我们去查看slf4j-api的源码,在源码中看到了StaticLoggerBinder这个类,猜想应该是slf4j-api在打包过程中有动作,删除了自己包中的那个类,结果不出所料,确实是pom中的ant-task给处理了,pom中处理方式如下:

  
        org.apache.maven.plugins  
        maven-antrun-plugin  
          
            
            process-classes  
              
             run  
              
            
          
          
            
            Removing slf4j-api's dummy StaticLoggerBinder and StaticMarkerBinder  
              
            
          
        

       打出来的slf4j-api的包是"不完整"的,只有找到包含StaticLoggerBinder这个类的包才可以,于是slf4j-log4j和logback-classic都提供了这个类。另外,slf4j-log4j和logback以及slf4j-jdk14是不能同时和slf4j共存的,也就是说只能有一个实现存在,不然启动会提示有多个绑定。
       同时这个图中桥阶层和对应的实现jar是不能共存的,比如log4j-over-slf4j和slf4j-log4j,jul-to-slf4j和slf4j-jdk14,这个很好理解,会有死循环,启动也会报错。也就是说jar之前有互斥性。
       当然slf4j也提供了可以把对slf4j的调用桥接到JCL上的工程包——slf4j-jcl,可以看出slf4j的设计者考虑非常周到,想想这样的情况:遗留系统使用的是JCL+log4j,因为系统功能演进,依赖了其他业务线的库,恰好那个库依赖了slf4j-api,并且应用需要关心这个库的日志,那么就需要转接日志到JCL上即可。细心的你可能一经发现,slf4j-jcl和jcl-over-slf4j也是互斥的,太多互斥的了.......
       对于log4j2的加入,也很简单,和logback是很相似的,如下图:



红线依然表示依赖的互斥,当然log4j-slf4j-impl也会和logback-classic、slf4j-log4j、slf4j-jdk14互斥。

常见的问题:

  • slf4j-api和实现版本不对应,尤其是1.6.x和1.5.x不兼容,如果没有特殊需求,直接升级到最新版本。

  • slf4j的多个实现同时存在,比如slf4j-log4j和logback-classic,排除其中一个即可。

  • log4j和logback不能同时使用?可以同时使用,这两个并不矛盾,遗留系统可能直接使用了log4j的代码,并且不能通过log4j-over-slf4j桥接,那么可以让他继续使用log4j,这里(http://www.slf4j.org/legacy.html)有详细的介绍。

  • 该如何选用这些呢?建议在非特殊情况下,都使用slf4j-api+logback,不要直接使用日志实现,性能没什么影响。对于要提供给别人的类库,建议使用slf4j-api,使用方可以自由选择具体的实现,并且建议类库不要依赖具体的日志实现。对于自己的桌面小应用,可以直接使用log4j,毕竟只是随便做做。

  • logback因为木有spring提供的启动listener,所以要自己写?可以看看这里(https://github.com/qos-ch/logback-extensions),开源社区已经做好了。

  • 日志系统一般不会影响到系统性能,除非你的系统对性能非常苛刻,如果这样你可以考虑使用Blitz4j(https://github.com/Netflix/blitz4j),这个是Netflix(http://netflix.github.io/)社区对log4j的性能改进版,不过他们依然建议去使用log4j或者logback。

你可能感兴趣的:(Java日志概述)