每一个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,但我这样改了没用。后来在一个英文网站上看到一个遇到同样问题的帖子,他是这样改的:
log4jConfigLocation
/WEB-INF/config/log4j.properties
org.springframework.web.util.Log4jConfigListener
这样改了问题就解决了,不用再修改ContextLoaderListener。