001-log-log体系-log4j、jul、jcl、slf4j,日志乱象的归纳与统一

一、概述

log4j→jul→jcl→slf4j之后就开始百花齐放【slf4j适配兼容新老用户】

1.1、log4j阶段  

  在JDK出现后,到JDK1.4之前,常用的日志框架是apache的log4j。

1.2、jul阶段

  在JDK1.4后,sun公司增加了一个包为java.util.logging,简称为jul,用以对抗log4j。

1.3、jcl阶段【commons-logging】

  开源项目都使用的是 log4j,log4j 已经成了事实上的标准,但由于又有一部分开发者在使用 sun logger,既jul,因此 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为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根据指定顺序查找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中的正常使用。

  其实就是:jcl默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用jul(jdk自带的) 实现,再没有则使用jcl内部提供的SimpleLog 实现。

  代码使用

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\省略

  至于这个Log具体的实现类,JCL会在ClassLoader中进行查找。这么做,有三个缺点,缺点一是效率较低,二是容易引发混乱,三是在使用了自定义ClassLoader的程序中,使用JCL会引发内存泄露。

1.4、slf4j阶段【slf4j-api】

  SLF4J 全称 Simple Logging Facade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过Facade Pattern门面模式对外提供一些Java Logging API。这些对外提供的核心API其实就是一些接口以及一个LoggerFactory的工厂类。
  在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定想用的Log库。
  按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Apache Commons Logging。即使以后又出现更新的其他日志组件,也能完全适应。

  log4j的作者觉得jcl不好用,自己又写了一个新的接口api,那么就是slf4j。关于slf4j的集成图如下所示

    

  如图所示,应用调了sl4j-api,即日志门面接口。日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,上图红框中的组件即是对应的各种桥接器!

  使用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)

  logback是slf4j-api的天然实现,不需要桥接包就可以使用。与commons loging(JCL)不同的是其采用在classPath加入桥接jar包来表示具体采用哪种实现(静态绑定)

  我们在代码中需要写日志,变成下面这么写

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

  在代码中,并不会出现具体日志框架的api。程序根据classpath中的桥接器类型,和日志框架类型,判断出logger.info应该以什么框架输出!注意了,如果classpath中不小心引了两个桥接器,那会直接报错的!

  因此,在阿里的开发手册上才有这么一条

强制:应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架 SLF4J 中的 API 。使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一。

二、日志乱象的归纳与统一

2.1、spring日志与log4j2等非jcl实现结合【jcl-over-slfj.jar】

  Spring采用的JCL【log4j、jul、simpleLog】中不包含log4j2(LoggerFactoryImpl源码中定义的数组中不包含log4j2),运行时,JCL从数组顺序寻找日志的实现,如果没有引入其他实现,最终则会用JUL打印日志, 

    

  这时会出现的问题,Spring 打印日志方式和应用程序打印日志不统一,错误排除时比较困难。而且应用程序和Spring框架,日志不统一,太乱了,还得搞2份日志配置文件。

  为了让Spring和我们的应用程序,采用统一的log4j2 日志体系,需要加入适配器。改善上面应用程序和框架日志不统一的问题(加入适配器后):

    

  中间这个适配器:其实就是jcl-over-slfj.jar(jcl适配slf4j),加入下面的依赖即可,再去除commons-logging。

 

posted @ 2019-06-21 09:14  bjlhx15  阅读(1345)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭