Java日志体系

603 阅读7分钟
背景:
Java中的日志非常多,总结一下,大概有以下几种:
日志框架
描述

相关jar
Jdk logging
jdk 自带的
slf4j-jdk14、jul-to-slf4j
log4j
Apache 的一个开放源代码项目
log4j、slf4j-log4j12、log4j-over-slf4j
log4j2
Log4j的加强版
log4j-api、log4j-core、log4j-slf4j-impl
logback
Logback 是由 log4j 创始人设计的又一个开源日记组件
logback-core、logback-classic
Jcl
commons-logging是Apache commons类库中的一员、能够选择使用Log4j还是JDK Logging,但是他不依赖Log4j,JDK Logging的API
commons-logging、slf4j-jcl 、jcl-over-slf4j

背景/发展史
那就要从Java Log的发展历程开始说起。
  1. log4j(作者Ceki Gülcü)出来时就等到了广泛的应用(注意这里是直接使用),是Java日志事实上的标准,并成为了Apache的项目。
  2. Apache要求把log4j并入到JDK,SUN拒绝,并在jdk1.4版本后增加JUL(java.util.logging)。
  3. 毕竟是JDK自带的,JUL也有很多人用。同时还有其他日志组件,如SimpleLog等。这时如果有人想换成其他日志组件,如log4j换成JUL,因为api完全不同,就需要改动代码。
  4. Apache见此,开发了JCL(Jakarta Commons Logging),即commons-logging-xx.jar。它只提供一套通用的日志接口api,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样应用程序可以在运行时选择自己想要的日志实现组件。
  5. 这样看上去也挺美好的,但是log4j的作者觉得JCL不好用,自己开发出slf4j,它跟JCL类似,本身不替供日志具体实现,只对外提供接口或门面。目的就是为了替代JCL。同时,还开发出logback,一个比log4j拥有更高性能的组件,目的是为了替代log4j。
  6. Apache参考了logback,并做了一系列优化,推出了log4j2

JCL(Apache Commons Logging)
之前叫Jakarta Commons Logging,简称JCL,是Apache提供的一个通用日志API,可以让应用程序不再依赖于具体的日志实现工具。Apache commons-logging是JCL的标准实现。commons-logging包中对其它一些日志工具,包括Log4J、Avalon LogKit、JUL等,进行了简单的包装,可以让应用程序在运行时,直接将JCL API打点的日志适配到对应的日志实现工具中。


JCL为每一种日志实现采用了一个适配器,具体采用哪个、是动态的根据指定顺序查找classPath是否存在相应日志的实现。如果JCL运行时没找到任何一种第三方的日志实现,则就用jdk14自带的java.util.logging(JUL)。假如你的maven工程pom.xml里加入了log4j的依赖,运行时JCL找到即可动态绑定。jcl加载时按照顺序log4j>jdk13>jdk14> simpleLog依次加载。
但是这么做,有三个缺点,缺点一是效率较低,二是容易引发混乱,三是在使用了自定义ClassLoader的程序中,使用JCL会引发内存泄露。
因此就出现了静态绑定。

验证一下:
1.在pom文件中只加入 commons-logging 包,那么按照动态加载顺序,找不到log4j,最后会使用jdk自带的log包。

<dependency>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     <version>1.2</version>
 </dependency>

public class HelloJcl {
    public static void main(String[] args) {
        Log log = LogFactory.getLog(HelloJcl.class);
        log.error("error message jcl");
    }
}

输出:
可以看到这就是找的jdk默认的log结果。
以下显示用jdk的log

public class HelloJul {
    public static void main(String[] args) {
        java.util.logging.Logger logger
                = java.util.logging.Logger.getLogger(HelloJul.class.getName());
        logger.info("hello jurl info222");
    }
}
输出:
可以看到和上面的输出格式一摸一样。

如果加上log4j的包:

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
输出:
可以发现代码没变格式改了。

slf4j(Simple Logging Facade For Java):
SLF4J 全称 Simple Logging Facade for Java(简单日志门面)。与JCL类似,本身不替供日志具体实现,只对外提供接口或门面。因此它不是具体的日志解决方案,而是通过Facade Pattern门面模式对外提供一些Java Logging API。
在使用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作为日志门面的话,你如何去配合使用其他日志实现组件,需要一下的组合:
slf4j + logback 

   slf4j-api.jar + logback-classic.jar + logback-core.jar 

slf4j + log4j

   slf4j-api.jar + slf4j-log4j12.jar + log4j.jar 

slf4j + jul

  slf4j-api.jar + slf4j-jdk14.jar 

也可以只用slf4j无日志实现

  slf4j-api.jar + slf4j-nop.jar


SLF4J提供了统一的记录日志的接口(LoggerFactory),只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
logback是slf4j-api的天然实现,不需要桥接包就可以使用。
与commons loging(JCL)不同的是其采用在classPath加入桥接jar包来表示具体采用哪种实现(静态绑定)
比如:slf4j方式,使用log4j日志组件,需要pom文件加入:

<!-- 加入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>
因此JCL方式的commons-logging 是动态查找绑定。
SLF4J是静态绑定,需要加桥接包。

但是还有问题:
我的应用程序想使用log4j2打印日志;而Spring采用的JCL中不包含log4j2(LoggerFactoryImpl源码中定义的数组中不包含log4j2),运行时,JCL从数组顺序寻找日志的实现,如果没有引入其他实现,最终则会用JUL打印日志,如下图:


这两种我该怎么给他统一呢?
为了解决这种问题,出现了slf4j的适配器。

SLF4J的适配
为了解决上面的问题,slf4j出现适配器,slf4j支持各种适配,无论你现在是用哪种日志组件,你都可以通过slf4j的适配器来使用上slf4j。只要你切换到了slf4j,那么再通过slf4j用上实现组件。
总的来说就是如下图:


官网附图:


其实总的来说,无论就是以下几种情况
 你在用JCL 

    使用jcl-over-slf4j.jar适配 

 你在用log4j 

    使用log4j-over-slf4j.jar适配

 你在用JUL 

    使用jul-to-slf4j.jar适配


上面问题,spring使用jcl,我要使用log4j2问题就可以这么解决:
对应的pom文件:

<!--slf4j门面 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.3</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.13</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.3</version>
</dependency>

jar 死循环问题:
如果引了四个jar包,slf4j-api-xx.jar,slf4j-log4j12-xx.jar,log4j-xx.jar,log4j-over-slf4j-xx.jar。


如上图所示,在这种情况下,你调用了slf4j-api,就会陷入死循环中!slf4j-api去调了slf4j-log4j12,slf4j-log4j12又去调用了log4j,log4j去调用了log4j-over-slf4j。最终,log4j-over-slf4j又调了slf4j-api,陷入死循环!