每一个Java程序员都知道日志对于任何一个Java应用程序,尤其是服务端程序是至关重要的,而很多程序员也已经熟悉各种不同的日志库如java.util.logging、Apache log4j、logback
。
在这篇文章中,我们将学习为什么使用SLF4J
比log4j
或者java.util.logging
要优秀。
SLF4J不同于其他日志类库,与其它有很大的不同。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世界的日志标准。让我们再看看几个使用SLF4J而不是log4j、logback或者java.util.logging的理由。
SLF4J对比Log4J,logback和java.util.Logging的优势
- 使用SLF4J写日志语句的主要出发点是使得你的程序独立于任意特定的日志类库,依赖于特定类可能需要不同与你已有的配置,并且导致更多维护的麻烦。
- 占位符(place holder)的使用。
在代码中表示为{}
的特性。占位符是一个非常类似于在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做Log4J的日志记录
为了使用SLF4J,你不仅需要包含SLF4J的API jar
包,例如 slf4j-api-1.6.1.jar
,还需要相关Jar包,这取决于你在后台使用的日志类库。如果你想要使用和Log4J 一起使用SLF4J ,Simple Logging Facade for Java,,你需要包含以下的Jar包在你的classpath中,取决于哪个SLF4J和你在使用的Log4J的版本。例如:
-
slf4j-api-1.5.8.jar
– JAR for SLF4J API -
log4j-1.2.16.jar
– JAR for Log4J API -
slf4j-log4j12-1.5.8.jar
– Log4J Adapter for SLF4J
如果你在使用Maven去管理你的项目依赖,你只需要包含SLF4J JAR包,maven会包含它的依赖的相关包。为了和SLF4J一起中使用Log4J,你可以包含以下的依赖在你项目中的pom.xml。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
使用实例
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4js {
private static Logger logger = LoggerFactory.getLogger(Slf4js.class);
Integer t;
Integer oldT;
public void setTemperature(Integer temperature) {
oldT = t;
t = temperature;
logger.error(" Temperature set to {}. Old temperature was {}. ", t, oldT);
if (temperature.intValue() > 50) {
logger.info(" Temperature has risen above 50 degrees. ");
}
}
public static void main(String[] args) {
Slf4js slf4j = new Slf4js();
slf4j.setTemperature(1);
slf4j.setTemperature(55);
}
}
常见问题
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();
解决方案:
1.修改slf的源代码,将这个变量有私有改为公有,再打包,问题可解决。
2.slf4j-api.jar 删除,再导入同版本的slf4j-api-1.5.6.jar 和slf4j-log4j12-1.5.6.jar ,问题可解决。
问题二
问题描述:
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
文件夹中即可。
===================================
做一个SSH项目,tomcat启动时出现以下问题:
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
。
总结
在你的开源或内部类库中使用SLF4J会使得它独立于任何一个特定的日志实现,这意味着不需要管理多个日志配置或者多个日志类库。
SLF4J提供了基于占位符的日志方法,不需要检查isDebugEnabled(), isInfoEnabled()等等,提高了代码可读性。
通过使用SLF4J的日志方法,你可以延迟构建日志信息(Srting)的开销,直到你真正需要,这对于内存和CPU都是高效的。
作为附注,更少的暂时的字符串意味着垃圾回收器(Garbage Collector)能够做更多的工作,这意味着你的应用程序有为更好的吞吐量和性能。
这些好处只是冰山一角,你将在开始使用SL4J和阅读其中代码的时候知道更多的好处,建议使用SLF4J做日志而不是使用包括Log4J在内的其他日志API。
网友评论