java日志和SLF4J随想

原文地址 译者:刘小刘

本文漫谈java中的日志:以前怎样使用日志,以及类似SLF4J的库为我们带来了什么。

日志是创建软件时的基本需求之一,常见的用例如:

  • 软件开发过程中的调试
  • 生产环境下诊断bug
  • 出于安全目的而跟踪访问
  • 创建统计使用的数据
  • 等等


无论用途为何,日志都应该是详尽、可配置和可靠的。
历史
在早期,java日志使用System.out.println(), System.err.println() 或 e.printStackTrace()。调试信息输出到标准输出System.out,错误信息输出到标准错误System.err。在生产环境中,二者都被重定向:前者重定向到null(注:不输出),后者重定向到需要的日志文件。这种做法够用但有很大的缺点:不可配置。它是个是或否的开关,要么全记录要么完全不,不能在某一层或某个包上关注具体的日志。

Log4J充当了救星,它满足了人们对日志框架的几乎所有需求。它引入了许多日志框架中今天仍在使用的概念(它是我首个使用的框架所以请原谅我若某个概念其实不是它发明的):

  • Logger的概念,每个logger可以单独配置
  • Appender的概念,每个appender可以将日志输出到它想要的任何地方(文件、数据库、消息等等)
  • Level的概念,开发人员可以单独配置是否输出每条日志

之后,Sun意识到需要在JDK中提供日志特性,它没有直接使用Log4J,而是模仿Log4J创建了自己的API。然而,新API的完成度不及Log4J。如果你想使用JDK1.4的日志API,你可能必须创建自己的Appender-在java API中叫Handler-因为能直接用的日志目的地只有控制台和文件。

使用这些框架都需要多份配置,因为不管你选择哪个,至少有一个你的依赖会使用另一个。Apache Commons Logging是一个将它自己与日志框架连接的API桥梁。库应该调用commons-logging,这样库使用的实际框架和你的工程是相同的,而不是它强制使用的。现实不总是这样,所以Commons Logging没有解决双重配置问题。此外,Commons Logging还会遇到类加载的问题,导致NoClassDefFoundError报错。

最后,Log4J核心的开发者由于此处不便细说的原因退出了Log4J工程。他创建了另一个日志框架,这个本应成为Log4J第二版的日志框架被命名为SLF4J。

一些奇怪的事实

以下是前述框架困扰我的一些事实,它们不一定都是缺陷但值得指出来:

  • Log4J通过maven强制依赖JMS, Mail和JMX,意味着如果你不嫌麻烦特意排除它们那这些就会出现在你工程的类路径中。
  • 类似地,Commons Logging通过maven强制依赖Avalon(另一个日志框架),Log4J, LogKit和Servlet API(!)
  • Log4J中始终包含一个Swing日志查看器,即使它是用在无需展现的环境中,例如批处理或应用服务器。
  • Log4J 1.3版的主页重定向到1.2版,而2.0版还在实验室阶段。

选择哪个框架?

Log4J是可以选择的框架(对大多数)但它不再开发了。1.2版是参考,1.3版废弃了而2.0还处在它的早期阶段。
Commons Logging是作为库的好选择(相比应用),但我无法忍受类加载器的问题,一次就够了(最后,我抛开Commons Logging直接用了Log4J).
JDK1.4日志是标准而且不存在多版本共用的问题,但它缺少太多特性,如果不二次开发如数据库适配器或其它就不能使用。太糟了。。。但仍未回答这个问题:选择哪个框架?

最近,我公司的架构师决定使用SLF4J,为什么会选择它?

SLF4J
SLF4J不及Log4J使用普遍,因为许多架构师和开发者熟悉Log4J而不知道SLF4J,或不关注SLF4J而坚持使用Log4J。此外,Log4J满足了许多工程的所有日志需求。不过,有趣的是,Hibernate使用了SLF4J。它拥有一些Log4J没有的美好特性。

简单的语法

看这个Log4J示例:
Logger.debug("Hello " + name);
由于字符串拼接的问题(注:上述语句会先拼接字符串,再根据当前级别是否低于debug决定是否输出本条日志,即使不输出日志,字符串拼接操作也会执行),许多公司强制使用下面的语句,这样只有当前处于DEBUG级别时才会执行字符串拼接:
if (logger.isDebugEnabled()) {

LOGGER.debug(“Hello ” + name);
}

它避免了字符串拼接问题,但有点太繁琐了是不是?相对地,SLF4J提供下面这样简单的语法:
LOGGER.debug("Hello {}", name);
它的形式类似第一条示例,而又没有字符串拼接问题,也不像第二条那样繁琐。

SLF4J API和实现
此外,SLF4J很好地解耦了API和实现,所以你在开发环境和生产环境中使用它的API都可以极佳地适配。例如,你可以强制使用SLF4J的API,而保持生产环境中用了几年的旧的Log4J.properties文件。SLF4J的日志实现是LogKit。

SLF4J桥接
SLF4J具有桥接的特性,你可以移除你的工程及其依赖组件使用的所有Log4J和commons-logging包,只使用SLF4J。
SLF4J为每一种日志框架提供一个JAR包:它模仿其它日志框架的API但将实现引向SLF4J的API(进而使用环境中真实的框架)。一点警告:小心不要让classpath中同时出现同一种日志的桥接库和实现库,否则会陷入循环。例如,使用Log4J桥接库时,每个Log4J的API被引向SLF4J,如果SLF4J的Log4J实现同时存在,会再次引向Log4J,这样循环下去。

SLF4JAPI和Log4J实现
综合所有考虑,我的建议是使用SLF4J的API和Log4J的实现。这样,你仍像以前一样配置Log4J,但调用更简单的SLF4J接口。要这样做,你需要:

操作 位置 描述
加入classpath slf4j-api.jar* 主API,没有它就无法使用SLF4J
slf4j-log4j.jar* SLF4J的Log4J实现
jul-to-slf4j.jar* 允许重定向 JDK 1.4日志到 SLF4J
jcl-over-slf4j.jar* 重定向commons-logging调用到SLF4J
从classpath移除 commons-logging.jar* 会跟 jcl-over-slf4j.jar里的commons-logging API冲突
SLF4JBridgeHandler.install()** 主应用 重定向JDK 1.4日志调用到SLF4J
* Jar名可能包含版本
**只在你需要单一入口或少量调用

如果你的应用在应用服务器中运行,你可能需要修改它的库和/或配置来达成以上修改。
更多:
SLF4J项目
Log4J项目
Commons Logging项目
Commons Logging类加载问题

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: java日志和SLF4J随想

  • Trackback 关闭
  • 评论 (0)
  1. 暂无评论

return top