java.util.logging、Apache log4j、logback
。一、SLF4J使你的代码独立于任意一个特定的日志API
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的优势
1、使用SLF4J写日志语句的主要出发点是使得你的程序独立于任意特定的日志类库,依赖于特定类可能需要不同于你已有的配置,并且导致更多维护的麻烦。
2、占位符(place holder)的使用。
在代码中表示为{}
的特性。占位符是一个非常类似于在String的format()
方法中的%s
,它会在运行时被某个提供的实际字符串所替换。
这不仅降低了你代码中字符串连接次数,而且还节省了新建的String对象。因为String对象是不可修改的并且它们建立在一个String池中,它们消耗堆内存( heap memory)而且大多数时间他们是不被需要的,例如当你的应用程序在生产环境以ERROR级别运行时候,一个String使用在DEBUG语句就是不被需要的。
通过使用SLF4J,你可以在运行时延迟字符串的建立,这意味着只有需要的String对象才被建立。
而如果你已经使用log4j,那么你已经对于在 if 条件中使用debug语句这种变通方案十分熟悉了,但SLF4J的占位符就比这个好用得多。
3、使用对比
// 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-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 APIlog4j-1.2.16.jar
– JAR for Log4J APIslf4j-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
3、其他情形下的问题解决:
(1)在Eclipse中开发相关项目时,在控制台经常看到如下信息
log4j:WARN No appenders could be found for logger
log4j:WARN Please initialize the log4j system properly.
log4j.properties
放到 WEB-INFclasses
文件夹中即可。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。
作者:七弦桐语、链接:https://www.jianshu.com/p/32e2a7254c03