00.java⽇志之发展史

524 阅读8分钟

java日志系列全解

# 00.java⽇志之发展史

# 01.java日志之原生的日志框架jul

# 02.java日志之log4j入门及xml配置详解

# 03.java日志之log4j的基础组件和各种Appender

# 04.java日志之jcl门面

# 05.java日志之slf4j门面

# 06.java日志之logback源码和xml解析

# 07.java日志之logback内部状态数据Status

# 08.java日志之logabck各环境配置

# 09.java日志之logback的appender标签及其子标签全解析

常用日志框架

  按照发布时间顺序排列:Log4j(reload4j) --> JUL --> JCL --> SLF4J --> Logback --> Log4j2

log4j、slf4j、log4j2、jul、jcl等⽇志和各种桥接包的关系

⼀切事情的发展都是有缘由的,java的⽇志为啥会发展成现在这个样⼦,我们来梳理下java⽇志的发展过程。

1. System.out 或 System.err

最初是没有我们使⽤的这些⽇志技术的,最开始的⽇志打印⽅式是这样:

System.out.println("this is out!");
System.err.println("this is err!");

把所有的信息都一股脑输出到控制台,不灵活不可配置,要么全部打印要么不要打印,更没有⽇志级别的管理。

2. log4j

始于 1996 年,作为记录E.U. SEMPER (Secure Electronic Marketplace for Europe)项目跟踪信息的 API。经过大量的完善和蜕变,最初的 API 终于演进为 Log4j,一个在 Java 社区流行的日志类库。

log4j一经推出就异常⽕爆,提到log4j就不得不提其主要贡献者:Ceki Gülcü,后来log4j成了apache基⾦会的⼀员,让log4j⼀度称为了业界的⽇志标杆,apache据说建议sun把log4j引⼊到java的jdk中,后来sun拒绝了,并采纳了这个建议,⾃⼰出了⼀个⽇志库,也就是后文中的jul。

Log4j 1.x 已在各种应用程序中得到广泛采用和利用。它的最终版本于 2012 年发布,并于 2015 年 8 月宣布 Log4j 1.x 已停止维护

Log4j官方源码和文档链接:logging.apache.org/log4j

也可以参考笔者自己写的关于log4j底层的两篇文章:

# 02.java日志之log4j入门及xml配置详解

# 03.java日志之log4j的基础组件和各种Appender

3. jul(java util logging)

sun在2002年2⽉推出了java 1.4发布,sun竟然推出了⾃⼰的⽇志库java util logging,其实很多⽇志思想也都仿照log4j,毕竟log4j已经⽐较成熟了,显然log4j更加成熟。

Java 原生的日志框架,使用时不需要引入第三方类库,相对其它日志框架使用方便、功能简单,能够在小型应用中灵活使用

image.png

  1. Logger:记录器。应用程序通过获取 Logger 对象,调用其 API 来发布日志信息。Logger 对象通常是应用程序访问日志系统的入口程序;
  2. Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫;
  3. Formatter:它负责对日志事件中的数据进行转换和格式化。Formatter决定了数据在一条日志记录中的最终形式;
  4. Handler:每个 Logger都可以关联n个Handler。Logger会将日志交给关联的所有Handler处理,由它们负责输出日志,其具体的实现决定了日志记录的位置可以是控制台、文件等;
  5. Filter:过滤器。根据需要定制哪些消息会被记录,哪些消息会被放过。

参考链接:# 01.java日志之原生的日志框架jul

4. JCL(Jakarta Commons Logging)

提起jcl可能大家有点陌生,其实就是commons-logging-xx.jar组件。

apache针对刚出来的jul,有搞了⼀套jcl,打算⼀统⽇志江湖,指定⽇志标准,jcl是⽇志抽象层,默认有⼀个Simple log的实现,(就像jdbc⼀统数据访问层),让⽇志产品去实现它的抽象,只要你的⽇志代码依赖JCL接⼝就可以很⽅便的在Log4j和JUL之间切换。

JCL:全称 Jakarta Common Logging ,是 Apache 提供的一个通用日志 API,它为 “所有的 Java 日志实现”提供了一个统一的接口,它自身也提供了一个日志的实现,但功能非常弱(SimpleLog),一般不会单独使用它,它允许开发者使用不同的具体日志实现工具:Log4jJUL

但是好景不长,随着JCL的应⽤,⼈们发现jcl功能非常弱且不易扩展,而且她带来的问题⽐它解决的问题还要多,适逢乱世,出来解决这个问题的应⽤问世了,就是Slf4j

jcl只提供 log 接口,具体的实现则在运行时动态寻找。这样一来开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。JCL可以实现的集成方案如下图所示:

image.png

提到jcl,我们必须要知道jcl底层是根据什么依据来决定使用哪种日志实现,看看以下jcl中的源码就懂了:

// jcl日志门面支持的日志实现数组 
private static final String[] classesToDiscover = { 
    "org.apache.commons.logging.impl.Log4JLogger", 
    "org.apache.commons.logging.impl.Jdk14Logger", 
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger", 
    "org.apache.commons.logging.impl.SimpleLog" };

Log result = null; 
for(int i = 0; i < classesToDiscover.length && result == null; ++i) { 
    result = createLogFromClass(classesToDiscover[i], logCategory, true); 
}
return result;

硬编码了数组,不包含log4j2和logback的最新实现

以上代码总结成一句话:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用jul(jdk自带的) 实现,否则使用jcl内部提供的SimpleLog实现

另外JCL会在ClassLoader中进行查找log具体实现。这么做,有三个缺点,

  1. 效率较低
  2. 容易引发混乱
  3. 在使用了自定义ClassLoader的程序中,使用JCL会引发内存泄露

也可以参考笔者自己写的关于jcl底层的文章:

# 04.java日志之jcl门面

5. Slf4j(simple Logging Facade for java)

核心功能:日志框架的绑定、日志框架的桥接

还是上面提到的ceki⼤佬(也是log4j的主要贡献者),由于某些原因离开了apache,他也觉得jcl问题很多,于是在2005年⾃⼰撸了⼀个新东西,也是⼀套⽇志接⼝,也有称之为⽇志门⾯,slf4j诞⽣了,并且剑指jcl,并且后来也证明了,slf4j⽐jcl要更加优秀。

但是由于slf4j问世⽐较晚,⽽且还只是⼀套接⼝,并不是⼀个可⽤的⽇志产品,而且现有的⽇志产品虽然不完美但是不⽤⾃⼰去实现,如jul或者log4j,所以对slf4j的推⼴造成了很⼤的阻⼒,这个时候ceki⼤佬⼜出现了,他键盘⼀敲,不就是没有实现吗,我来!

⼤佬⼀顿操作撸出了slf4j的桥接包,也就是⼀种适配器模式

在有了桥接包之后,⽇志框架关系如下图:

image.png

SLF4J提供的桥接包:

  • slfj-log4j12.jar (表示桥接 log4j)
  • slf4j-jdk14.jar(表示桥接jdk Looging)
  • sIf4j-jcl.jar(表示桥接 jcl)
  • log4j-slf4j-impl(表示桥接log4j2)
  • logback-classic(表示桥接 logback)

但是由于很多应⽤依赖了JCL,⽽没有它的桥接包,这个时候ceki:我⼜来啦,jcl的桥接包也来了

image.png

对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接

基于log4j的确定,⼤佬追求完美的路⼜往前迈了⼀步,logback诞⽣了。

SLF4J的官方网站:www.slf4j.org

6. logback

logback出现于slf4j之后,logback默认实现了slf4j的接口,使用logback时我们都结合使用了slf4j作为上层日志门面。

7. Log4j2

2012年apache推出了新项⽬,log4j2,因为log4j2完全不兼容log4j1.x,⽽且最巧的是log4j2⼏乎涵盖了logback的全部新特性,log4j2也⾼了分离式设计,分化成log4j-api和log4j-core,这个log4j-api也是⽇志接⼝,log4j-core才是⽇志产品。

现在我们有了三个⽇志皆苦,4个⽇志产品,当然apache也很清楚他们的⼯作,他们推出了log4j2的桥接包,哈哈。

总结到这可以发现:

1.接⼝给了⽆限可能,不写接⼝没有任何扩展性可⾔

2.没有什么问题是加⼀层适配器解决不了的,如果有那就再加⼀层。

连接了⽇志的整个框架,在使⽤的时候我们有什么启发呢

  1. 使⽤⽇志接⼝的api⽽不是⽇志产品的api,这样也符合依赖倒置原则

  2. 依赖选择⼀个即可

  3. 把⽇志产品依赖设置为optional和runtime scope 其中optional是为了依赖不会被传递,⽐如别⼈引⽤了这个jar,就会被迫使⽤不想使⽤的依赖

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j.version}</version>
    <optional>true</optional>
</dependency>

  ⽽scope设置为runtime,是可以保证⽇志的产品的依赖只有在运⾏时需要,编译时不需要,这样,开发⼈员就不会在编写代码的过程中使⽤到⽇志产品的API了

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
</dependency>

部分内容参考:

www.jianshu.com/p/39ced0694…

www.cnblogs.com/antLaddie/p…

# 一个著名的日志系统是怎么设计出来的