Java 常用日志框架介绍
1 概述
对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。java 领域存在多种日志框架,目前常用的日志框架包括 Log4j 1,Log4j 2,Commons Logging,Slf4j,Logback,Jul。
2 常用类别介绍
2.1 Log4j
Apache Log4j 是一个基于 Java 的日志记录工具。它是由 Ceki Gülcü首创的,现在则是 Apache 软件基金会的一个项目。 Log4j 是几种 Java 日志框架之一。
2.2 Log4j 2
Apache Log4j 2 是 apache 开发的一款 Log4j 的升级产品。
2.3 Commons Logging
Apache 基金会所属的项目,是一套 Java 日志接口,之前叫 Jakarta Commons Logging,后更名为 Commons Logging。
2.4 Slf4j
类似于 Commons Logging,是一套简易 Java 日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写 Slf4j)。
2.5 Logback
一套日志组件的实现(Slf4j 阵营)。
2.6 Jul (Java Util Logging)
自 Java1.4 以来的官方日志实现。
看了上面的介绍是否会觉得比较混乱,这些日志框架之间有什么异同,都是由谁在维护,在项目中应该如何选择日志框架,应该如何使用? 下文会逐一介绍。
3 历史
1996 年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪 API(Tracing API)。经过不断的完善,这个 API 终于成为一个十分受欢迎的 Java 日志软件包,即 Log4j。后来 Log4j 成为 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。
后来(2006 年),Ceki Gülcü不适应 Apache 的工作方式,离开了 Apache。然后先后创建了 Slf4j(日志门面接口,类似于 Commons Logging)和 Logback(Slf4j 的实现)两个项目,并回瑞典创建了 QOS 公司,QOS 官网上是这样描述 Logback 的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
现今,Java 日志领域被划分为两大阵营:Commons Logging 阵营和 Slf4j 阵营。
Commons Logging 在 Apache 大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013 年底有人分析了 GitHub 上 30000 个项目,统计出了最流行的 100 个 Libraries,可以看出 Slf4j 的发展趋势更好:
Apache 眼看有被 Logback 反超的势头,于 2012-07 重写了 Log4j 1.x,成立了新的项目 Log4j 2, Log4j 2 具有 Logback 的所有特性。
4 关系
Log4j 2 与 Log4j 1 发生了很大的变化,Log4j 2 不兼容 Log4j 1。
Commons Logging 和 Slf4j 是日志门面(门面模式是软件工程中常用的一种软件设计模式,也被称为正面模式、外观模式。它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用)。Log4j 和 Logback 则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用者只需要关注接口而无需关注具体的实现,做到解耦。
比较常用的组合使用方式是 Slf4j 与 Logback 组合使用,Commons Logging 与 Log4j 组合使用。
Logback 必须配合 Slf4j 使用。由于 Logback 和 Slf4j 是同一个作者,其兼容性不言而喻。
5 实现机制对比
5.1 Commons Logging 实现机制
Commons Logging 是通过动态查找机制,在程序运行时,使用自己的 ClassLoader 寻找和载入本地具体的实现。详细策略可以查看 commons-logging-*.jar 包中的 org.apache.commons.logging.impl.LogFactoryImpl.java 文件。由于 Osgi 不同的插件使用独立的 ClassLoader,Osgi 的这种机制保证了插件互相独立, 其机制限制了 Commons Logging 在 Osgi 中的正常使用。
5.2 Slf4j 实现机制
Slf4j 在编译期间,静态绑定本地的 Log 库,因此可以在 Osgi 中正常使用。它是通过查找类路径下 org.slf4j.impl.StaticLoggerBinder,然后在 StaticLoggerBinder 中进行绑定。
6 项目中的选择
如果是在一个新的项目中建议使用 Slf4j 与 Logback 组合,这样有如下的几个优点。
1)Slf4j 实现机制决定 Slf4j 限制较少,使用范围更广。由于 Slf4j 在编译期间,静态绑定本地的 LOG 库使得通用性要比 Commons Logging 要好。
2)Logback 拥有更好的性能。Logback 声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在 Logback 中需要 3 纳秒,而在 Log4J 中则需要 30 纳秒。LogBack 创建记录器(logger)的速度也更快:13 毫秒,而在 Log4J 中需要 23 毫秒。更重要的是,它获取已存在的记录器只需 94 纳秒,而 Log4J 需要 2234 纳秒,时间减少到了 1/23。跟 JUL 相比的性能提高也是显著的。
3)Commons Logging 开销更高
4)Logback 文档免费。Logback 的所有文档是全面免费提供的,不像 Log4J 那样只提供部分免费文档而需要用户去购买付费文档。
7Slf4j 的使用
7.1 Slf4j 与其它日志组件的关系说明
1)Slf4j 的设计思想比较简洁,使用了 Facade 设计模式,Slf4j 本身只提供了一个 slf4j-api-version.jar 包,这个 jar 中主要是日志的抽象接口,jar 中本身并没有对抽象出来的接口做实现。
2)对于不同的日志实现方案(例如 Logback,Log4j…),封装出不同的桥接组件(例如 logback-classic-version.jar,slf4j-log4j12-version.jar),这样使用过程中可以灵活的选取自己项目里的日志实现。
7.2 Slf4j 与其它日志组件调用关系图
7.3 Slf4j 与其他各种日志组件的桥接说明
具体的接入方式参见下图:
8Slf4j 源码分析
8.1 slf4j-api-version.jar 中几个核心类与接口
8.2 Slf4j 调用过程源码分析,只加入 slf4j-api-version.jar,不加入任何实现包
8.2.1 示例代码
https://github.com/chlsmile/slf4j-demo
8.2.2 pom 核心配置如下
8.2.3 程序入口类如下
8.2.4 源码追踪分析
1)调用 LoggerFactory 的 getLogger()方法创建 Logger
2)调用 LoggerFactory 的 getILoggerFactory 方法来创建 ILoggerFactory
3)调用 LoggerFactory 的
performInitialization 方法来进行初始化
4)调用 LoggerFactory 的 bind()方法
5)调用 LoggerFactory 的
findPossibleStaticLoggerBinderPathSet()方法获取 StaticLoggerBinderPath 集合
6)调用 LoggerFactory 的
reportMultipleBindingAmbiguity()方法,记录绑定的 StaticLoggerBinder 信息
7)LoggerFactory 的
reportMultipleBindingAmbiguity()方法
8)LoggerFactory 的 bind()方法找不到 StaticLoggerBinder,
抛出 NoClassDefFoundError 异常
9)LoggerFactory 的 bind()方法捕获 NoClassDefFoundError 异常,匹配到 StaticLoggerBinder 关键词记录信息到控制台
10)LoggerFactory 的 performInitialization()方法内部调用 bind()方法结束
11)LoggerFactory 的 getLogger()方法内部 getILoggerFactory()方法调用完成,创建出 NOPLoggerFactory,然后由 NOPLoggerFactory 调用内部的 getLogger()方法,创建出 NOPLogger
12)App 类内部的 logger 实际为 NOPLogger,调用logger.info()方法实际调用的是 NOPLogger 的 info 方法
评论