每一个 Java 程序员都知道日志对于任何一个 Java 应用程序, 尤其是服务端程序是至关重要的, 而很多程序员也已经熟悉各种不同的日志库如 java.util.logging,Apache log4j,logback.
我强烈建议, 在 java 中任何新的代码开发, 都应使用 SLF4J 而不是任何的日志 API, 包括 log4J.
本文将解释为什么使用 SLF4J 比 log4j 或者 java.util.logging 要优秀?
一, 抽象
SLF4J(Simple logging Facade for Java)不是一个真正的日志实现, 而是一个抽象层( abstraction layer), 它允许你在后台使用任意一个日志类库. 如果是在编写供内外部都可以使用的 API 或者通用类库, 那么你真不会希望使用你类库的客户端必须使用你选择的日志类库.
如果一个项目已经使用了 log4j, 而你加载了一个类库, 比方说 Apache Active MQ-- 它依赖于于另外一个日志类库 logback, 那么你就需要把它也加载进去. 但如果 Apache Active MQ 使用了 SLF4J, 你可以继续使用你的日志类库而无语忍受加载和维护一个新的日志框架的痛苦.
总的来说, SLF4J 使你的代码独立于任意一个特定的日志 API, 这是一个对于开发 API 的开发者很好的思想. 虽然抽象日志类库的思想已经不是新鲜的事物而且 Apache commons logging 也已经在使用这种思想了, 但现在 SLF4J 正迅速成为 Java 世界的日志标准.
二, 占位符
在代码中表示为 {} 的特性. 占位符是一个非常类似于在 String 的 format()方法中的 %s, 它会在运行时被某个提供的实际字符串所替换.
这不仅降低了你代码中字符串连接次数, 而且还节省了新建的 String 对象.
因为 String 对象是不可修改的并且它们建立在一个 String 池中, 它们消耗堆内存 ( heap memory) 而且大多数时间他们是不被需要的, 例如当你的应用程序在生产环境以 ERROR 级别运行时候, 一个 String 使用在 DEBUG 语句就是不被需要的.
通过使用 SLF4J, 你可以在运行时延迟字符串的建立, 这意味着只有需要的 String 对象才被建立. 而如果你已经使用 log4j, 那么你已经对于在 if 条件中使用 debug 语句这种变通方案十分熟悉了, 但 SLF4J 的占位符就比这个好用得多.
- Log4j:
- if (logger.isDebugEnabled()) {
- logger.debug("Processing trade with id:" + id + "symbol:" + symbol);
- }
- SLF4J:
- logger.debug("Processing trade with id: {} and symbol : {}", id, symbol);
在 SLF4J, 我们不需要字符串连接而且不会导致暂时不需要的字符串消耗. 而是以一个以占位符和以参数传递实际值的模板格式下写日志信息. 你可能会在想万一我有很多个参数怎么办? 那么你可以选择使用变量参数版本的日志方法或者用以 Object 数组传递. 这是一个相当的方便和高效方法的打日志方法. 记住, 在生产最终日志信息的字符串之前, 这个方法会检查一个特定的日志级别是不是打开了, 这不仅降低了内存消耗而且预先降低了 CPU 去处理字符串连接命令的时间. 这里是使用 SLF4J 日志方法的代码, 来自于 slf4j-log4j12-1.6.1.jar 中的 Log4j 的适配器类 Log4jLoggerAdapter.
- public void debug(String format, Object arg1, Object arg2) {
- if (logger.isDebugEnabled()) {
- FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
- logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
- }
- }
三, 原因总结
在你的开源或内部类库中使用 SLF4J 会使得它独立于任何一个特定的日志实现, 这意味着不需要管理多个日志配置或者多个日志类库, 你的客户端会很感激这点.
SLF4J 提供了基于占位符的日志方法, 这通过去除检查 isDebugEnabled(), isInfoEnabled()等等, 提高了代码可读性.
通过使用 SLF4J 的日志方法, 你可以延迟构建日志信息 (Srting) 的开销, 直到你真正需要, 这对于内存和 CPU 都是高效的.
作为附注, 更少的暂时的字符串意味着垃圾回收器 (Garbage Collector) 需要做更好的工作, 这意味着你的应用程序有为更好的吞吐量和性能.
再次强调, 在 java 中任何新的代码开发, 都应使用 SLF4J 而不是任何的日志 API, 包括 log4J.
四, slf4j 与 log4j 常见冲突
4.1. 问题一
描述
- java.lang.IllegalAccessError: tried to access field org.slf4j.impl.Static..
- java.lang.IllegalAccessError: tried to access field org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory
问题原因: jar 文件版本冲突
类 org.slf4j.impl.StaticLoggerBinder 在 slf4j-API 中是类的公有静态变量:
public static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
而在 slf4j-log4j12(slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar 其中之一)中确是私有变量:
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
解决方案:
修改 slf 的源代码, 将这个变量有私有改为公有, 再打包, 问题可解决.
slf4j-API.jar 删除, 再导入同版本的 slf4j-API-1.5.6.jar 和 slf4j-log4j12-1.5.6.jar , 问题可解决.
4.2 问题二
问题描述:
- log4j:WARN No appenders could be found for logger (xxx.yyy.zzz).
- log4j:WARN Please initialize the log4j system properly.
问题解决:
在 src 下面新建 file 名为 log4j.properties 内容如下:
- # Configure logging for testing: optionally with log file
- log4j.rootLogger=WARN, stdout
- # log4j.rootLogger=WARN, stdout, logfile
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
- log4j.appender.logfile=org.apache.log4j.FileAppender
- log4j.appender.logfile.File=target/spring.log
- log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
- log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
其他情形下的问题解决:
在 Eclipse 中开发相关项目时, 在控制台经常看到如下信息:
- log4j:WARN No appenders could be found for logger
- log4j:WARN Please initialize the log4j system properly.
此处输出信息并不是错误信息而仅只是警告信息, 因为 log4j 无法输出日志, log4j 是一个日志输入软件包. 可以将 Struts 或 Hibernate 等压缩包解压, 内有 log4j.properties 文件, 将它复制到项目 src 文件夹或将 log4j.properties 放到 \web-INF\classes 文件夹中即可.
4.3 问题三
问题描述
- log4j:WARN No appenders could be found for logger (org.springframework.Web.context.ContextLoader).
- log4j:WARN Please initialize the log4j system properly.
问题解决
在网上查了一下, 多是说把 ContextLoaderListener 改为 SpringContextServlet, 但我这样改了没用. 后来在一个英文网站上看到一个遇到同样问题的帖子, 他是这样改的:
- <context-param>
- <param-name>log4jConfigLocation</param-name>
- <param-value>/Web-INF/config/log4j.properties</param-value>
- </context-param>
- <!-- 定义 LOG4J 监听器 -->
- <listener>
- <listener-class>
- org.springframework.Web.util.Log4jConfigListener
- </listener-class>
- </listener>
这样改了问题就解决了, 不用再修改 ContextLoaderListener.
来源: http://www.jianshu.com/p/4a6db981a245