历史

Java日志系统确实比较丰富,常用的有log4j、JUL、logback等等,同时伴随着日志系统的发展,出现了日志框架commons-logging和slf4j。

  • 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j(由Ceki创建)。
  • 后来Log4j成为Apache基金会项目中的一员,Ceki也加入Apache组织。后来Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议Sun引入Log4j到Java的标准库中,但Sun拒绝了。
  • 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,Log4j就已经成为一项成熟的技术,使得Log4j在选择上占据了一定的优势。
  • 接着,Apache推出了Jakarta Commons Logging,JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是Log4j,也可以是Java Util Logging。出色地完成了兼容主流的日志实现(log4j、JUL、simplelog)
  • 后来(2006年),Ceki不适应Apache的工作方式,离开了Apache。然后先后创建了Slf4j(日志门面接口,类似于Commons Logging) 和Logback(Slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
  • Java日志领域被划分为两大阵营:Jakarta Commons Logging阵营和Slf4j阵营。
  • Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出Slf4j的发展趋势更好。
  • Apache眼看有被Logback反超的势头,于2012-07重写了Log4j 1.x,成立了新的项目log4j2, 具有Logback的所有特性。

日志框架

日志框架的作用:

  1. 控制日志输出的内容和格式
  2. 控制日志输出的位置
  3. 日志文件相关的优化,如异步操作、归档、压缩等。
  4. 日志系统的维护
  5. 面向接口开发 - 日志的门面

门面型日志框架

  1. JCL:Apache基金会所属的项目,是一个通用日志API,可以让应用程序不再依赖于具体的日志实现工具。之前叫Jakarta Commons Logging,后更名为Apache Common Logging
  2. SLF4J:是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j)

记录型日志框架(实现)

  1. Jul (Java Util Logging):JDK中的日志记录工具,也常称为JDKLog、jdk-logging,自Java1.4以来的官方日志实现。
  2. Log4j:Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。
  3. Log4j2:一个具体的日志实现框架,是Log4j 1的下一个版本,与Log4j 1发生了很大的变化,Log4j 2不兼容Log4j 1
  4. Logback:一个具体的日志实现框架,和Slf4j是同一个作者,但其性能更好(推荐使用)。

日志库组件

java的日志库主要有以下三个组件:记录器(·Logger),处理器(Handler),过滤器(Filter),格式化器(Formatter)

记录器只是负责记录,将你想要保存的日志消息先保存起来。然后才发送给处理器,处理器帮你输出到指定的地点。
处理器有多种类型,如果是工作台处理器(ConsoleHandler),如果记录器将日志消息发送给工作台处理器,这个处理器就会输出到工作台上。

日志框架的选择

  • 单独使用log4j / JCL结合log4j
    • 这个是早几年最最流行的用法了,现在因为log4j本身的问题以及新的日志框架的涌现,已经逐步退出历史舞台了。具体怎么用自己去百度吧。
  • SLF4J结合Logback
    • 当下最流行的用法,SLF4J为使用场景最广泛的日志门面,加上Logback的天然实现,简单、统一、快速。
    • 引入的依赖:slf4j-api, logback-core, logback-classic
  • 单独使用Log4j2
    • Log4j2感觉就是SLF4J+Logback。log4j-api等价于SLF4J,log4j-core等价于Logback。
    • 引入的依赖:log4j-api, log4j-core

常见的问题

  1. slf4j-api和实现版本不对应,尤其是1.6.x和1.5.x不兼容,如果没有特殊需求,直接升级到最新版本。
  2. slf4j的多个实现同时存在,比如slf4j-log4j和logback-classic,排除其中一个即可。
  3. log4j和logback不能同时使用?可以同时使用,这两个并不矛盾,遗留系统可能直接使用了log4j的代码,并且不能通过log4j-over-slf4j桥接,那么可以让他继续使用log4j,详细的介绍查看官网说明
  4. logback因为没有spring提供的启动listener,所以要自己写?可以看看logback-extensions,开源社区已经做好了。
  5. 日志系统一般不会影响到系统性能,除非你的系统对性能非常苛刻,如果这样你可以考虑使用Blitz4j,这个是Netflix社区对log4j的性能改进版,不过他们依然建议去使用log4j或者logback。

JCL与Slf4j实现机制对比

门面型的日志框架主要就两个JCL(Commons Logging)和Slf4j,我们来简单了解下它们的区别:

JCL(Commons Logging)实现机制

JCL(Commons Logging) 是通过动态查找机制,在程序运行时,使用自己的ClassLoader寻找和载入本地具体的实现。详细策略可以查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。由于Osgi不同的插件使用独立的ClassLoader,Osgi的这种机制保证了插件互相独立, 其机制限制了Commons Logging在Osgi中的正常使用。

Slf4j实现机制

Slf4j在编译期间,静态绑定本地的Log库,因此可以在Osgi中正常使用。它是通过查找类路径下org.slf4j.impl.StaticLoggerBinder,然后在StaticLoggerBinder中进行绑定。

日志库使用示例

  • slf4j-api是slf4j接口,只引入slf4j-api包是打印不了日志的
  • 只引入记录型框架是能打印日志。

log4

早年,你工作的时候,在日志里使用了log4j框架来输出,于是你代码是这么写的

1
2
3
4
5
import org.apache.log4j.Logger;
//省略...
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
//省略...

jul

但是,岁月流逝,sun公司对于log4j的出现内心隐隐表示嫉妒。于是在jdk1.4版本后,增加了一个包为java.util.logging,简称为jul,用以对抗log4j。于是,你的领导要你把日志框架改为jul,这时候你只能一行行的将log4j的api改为jul的api,如下所示:

1
2
3
4
5
import java.util.logging.Logger;
//省略...
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
//省略...

可以看出,api完全是不同的。那有没有办法,将这些api抽象出接口,这样以后调用的时候,就调用这些接口就好了呢?

jcl

这个时候jcl(Jakarta Commons Logging)出现了,说jcl可能大家有点陌生,讲commons-logging-xx.jar组件,大家总有印象吧。JCL 只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。JCL可以实现的集成方案(log4j jul simplelog)

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

于是,你在代码里变成这么写了

1
2
3
4
5
6
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会引发内存泄露。

slf4j

于是log4j的作者(Ceki)觉得jcl不好用,自己又写了一个新的接口api,那么就是slf4j。

1
2
3
4
// 如果要在一个类上进行日志记录,且这个类有继承某个类,一般是在这个类的超类上进行
protected final Log logger = LogFactory.getLog(this.getClass());
// 子类就可以直接使用
this.logger.info()

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

1
2
3
4
5
6
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 。使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一。