Java日志体系概述
平时自己写代码很少使用日志,对Java日志这方面的知识也有所欠缺。现在工作中要求在代码中使用合理地打上一些日志,这几天也有去了解Java日志有关的一些知识,这篇文章也是对最近几天的学习进行一些总结。
为什么要有Java日志?
可能在写程序你也会有和我一些的疑问,为什么要用Java日志这类东西,如果要输出程序运行细节的话,使用System.out
或者 System.err
不就行了。这样子的想法大概我们的前辈也有经历过,在JDK1.3及以前版本中Java打日志只能使用以上方法输出到STDOUT流和STDERR流。但是其实使用这种方式记录日志很不灵活,例如你不能很轻松的更改日志级别、关闭日志或者对日志进行自定义操作。于是在1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。这是Log4j官网关于Log4j的介绍,同时也说明了为什么要使用日志记录。总结如下:
- 调试代码一种低技术含量的方法就是将日志语句插入到代码中。在大多中场景中,这也可能是唯一的方法,因为调试器不总是可用或者不适用(对于多线程应用程序和分布式应用通常是这种情况)
- 精确记录应用程序运行的上下文环境,一旦插入到代码中,日志输出的生成无需人工干预
- 日志输出可以保存在永久性介质(文件磁盘)中,以便以后进行研究
- 除了在开发周期中使用它外,足够丰富的日志记录包也可以视为审核工具
当然日志记录除了有以上优点外,也有它的缺点,比如记录日志可能会使程序运行变慢。
Java日志体系介绍
从96年(Log4j)到现在,可供我们使用的日志记录工具已经很多了,JUL(Java Util Logging)、JCL(Jakarta Commons Logging)、Log4j、SLF4J、Logback、Log4j2 等等。多得不知道如何去选择这些日志工具,甚至有点混乱了。在一个项目中如果你依赖了很多第三方库,你就会发现一件“有趣”的事情,你的应用程序可能会有好几个日志工具共存或者第三库使用的日志工具和你项目中使用的不一致,因此你必须耐心的去排除掉这些日志工具使其统一,如果没有对Java日志体系建立起足够的了解的话,这不是一件容易的事。(≖_≖ )
为了厘清现在所使用的Java日志工具,回溯到过去,了解Java日志体系发展历史是一种最为有效的途径了。
日志体系开端——Log4j
前面有说到,JDK开始是没有内置日志工具的,只能通过System.out
或者 System.err
记录程序运行状况。在1996年,Java才有了日志工具,Log4j。Log4j一经推出,就成为了业内的日志标杆。Log4j后来成为了Apache基金会项目中的一员,这也为后来的混乱局面埋下了伏笔(不是“官方正统”)。
混乱开端——Java“正统”日志工具JUL
在JDK1.4,sun公司推出了Java“正统”日志库**JUL(Java Util Logging)**企图对抗Log4j,Java日志工具局面开始混乱了。
一统天下的“暴君”——JCL(Jakarta Commons Logging)
在JUL(Java Util Logging)**出现后不久,开发人员就意识到需要一个统一通用的日志接口。这个时候,Apache推出了JCL(Jakarta Commons Logging)**,日志框架即日志抽象层,同时提供一个默认实现Simple Log。JCL的逻辑是这样子的:JCL将作为所有日志工具的框架(抽象访问层),让实际记录日志的日志工具去实现它的抽象,这样子只要你使用日志代码依赖的是JCL的接口,你就可以很方便的在各种日志工具做切换。
当然统一日志局面也是要有“相应能力”的,显然JCL在这方面做得不够好,开发人员发现了JCL还不够好,有些人甚至认为JCL造成的问题比解决的问题还多。
英雄登场——Slf4j&Logback
这里说的是“英雄”是指Ceki Gülcü(同时也是Log4j的作者),他觉得JCL不够好,所以开发出了一套更为优秀的日志框架Slf4j(Simple Logging Facade for Java),同时也提供一个比Log4j更为优秀的默认实现Logback。为了适配现有一些日志工具(Log4j,JUL),Ceki Gülcü也为一系列日志工具提供了桥接包,如slf4j-log4j12(Slf4j-Log4j桥接包)。当然只有日志工具的桥接包是不够的,如果当前应用使用了JCL日志框架,现在要使用Slf4j作为统一日志框架输出日志怎么办?桥接,当前桥接包不够那就再加一个,于是就有了slf4j-over-jcl(slf4j替换JCL)。
于是,现有的日志工具和日志框架的使用逻辑如下:
图片来源:https://segmentfault.com/img/bVbAMVm
注:使用桥接包时一定要注意避免环形依赖问题(会造成StackOverflow)。日志框架和日志工具或日志框架之间的两个桥接包一定不能同时存在。slf4j-xxx包作用是接口使用slf4j这一套,底层日志实现使用具体的xxx; xxx-over-slf4j包作用是对于xxx调用会转调slf4j接口。
王者归来——Log4j 2(Log4j-api&Log4j-core)
你以为这样子就结束了?为了维护在 Java 日志江湖的地位,Apache重写了Log4j 1.x,成立了新的项目Log4j 2, 且Log4j 2具有Logback的所有特性。Log4j-api作为全新的日志框架,Log4j-core则作为它的默认实现,除了具有Logback所有特性之外,还新增了很多其他新特性,具体在Log4j 2官网。当然,随着全新的日志框架和日志工具的出现,这次同样也带来了一大堆桥接包。具体如下:
图片来源:https://segmentfault.com/img/bVbAMVm
以上就是Java日志体系各阶段的发展历史了,到今天已然变成了“三分天下”的混乱局面。只怪当初的Sun公司不够争气,要是当初能像JDBC一样提出一套Java日志规范(接口)就没后面这么多事了。所以,面向接口编程是非常重要的!!!
日志框架和工具的选择
根据我写这篇文章在网上搜索文章的经历,目前Slf4j+Logback组合是当前最热门,刚好我工作中使用的也是这套。
如何排除项目中依赖的第三方包的日志依赖
在项目开发过程中,项目会根据需要引入一些第三方库,例如常用的 Spring,而 Spring 本身的日志实现使用了commons-logging(JCL),如果想使用 Slf4j+Logback 组合,这时候需要在项目中将 commons-logging排除掉,通常会用到以下 3 种方案,各有利弊,可以根据项目的实际情况选择最适合自己项目的解决方案。
采用 maven 的 exclusion 方案这种方案优点是 exclusion 是 maven 原生提供的,不足之处是如果有多个组件都依赖了 commons-logging,则需要在很多处增加 exclusion,比较繁琐。(我一般使用这种方法)
在 maven 声明 commons-logging 的 scope 为 provided,这种方案虽然简洁,但也有缺点,在调试代码时有可能导致 IDE 将 commons-logging 放置在 classpath 下,从而导致程序运行时出现异常。
在 maven 私服中增加类似于 99.0-does-not-exist 这种虚拟的版本号。这种方案好处在于声明方式比较简单,用 IDE 调试代码时也不会出现问题,不足之处是 99.0-does-not-exist 这种版本是 maven 中央仓库中可能不存在,需要发布到自己的 maven 私服中。
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>99.0-does-not-exist</version> </dependency>