log4j,slf4j,logback日志处理总结
分类:软件编程
阅读:134
作者:马晶晶
发布:2016-10-25 10:35:06

今天有时间看下TMBlog系统之前遗留的日志问题,就是采用了logback后之前的log4j等日志就无法输出了.

TMBlog 系统使用到了logback遇到了一个问题,明明已经使用了logback.xml但log4j还是提示警告

log4j:WARN No appenders could be found for logger (org.apache.commons.httpclient.HttpClient).
log4j:WARN Please initialize the log4j system properly.

我完美的想法是logback作为最牛逼的日志组件,应该把之前的log4j等实现接管过来吧,怎么没有呢?

我debug了一下,首先从出现警告的HttpClient开始,使用的是apache-commons-logging。其实只要搞懂这样代码做了什么事情,对日志系统就理解了。

private static final Log LOG = LogFactory.getLog(HttpClient.class);

首先这行代码在commons-logging内部实际执行的是:

getFactory().getInstance(clazz)

问题1,commong-logging如何查找LogFactory?

我就不翻译了,请看官方代码注释,4种情况写得很清楚明白:
logfactory获取方式.png

在我的情况下会执行到最后一种情况,也就是使用org.apache.commons.logging.impl.LogFactoryImpl。

问题2,logFactory如何创建logger?

LogFactoryImpl的调用栈如下:

LogFactoryImpl.getInstance(Class clazz)
LogFactoryImpl.getInstance(String name)
LogFactoryImpl.newInstance(String name)
LogFactoryImpl.discoverLogImplementation(String logCategory)

最后实现的时候按照一下顺序尝试创建Class,如果成功则返回:

org.apache.commons.logging.impl.Log4JLogger
org.apache.commons.logging.impl.Jdk14Logger
org.apache.commons.logging.impl.Jdk13LumberjackLogger
org.apache.commons.logging.impl.SimpleLog

Log4JLogger是一个代理类,其构造函数如下:

  1. public Log4JLogger(String name) {
  2. this.name = name;
  3. this.logger = getLogger();
  4. }
  5. public Logger getLogger() {
  6. if (logger == null) {
  7. logger = Logger.getLogger(name);
  8. }
  9. return (this.logger);
  10. }

可以看到实际使用的org.apache.log4j.Logger.getLogger(name)来创建logger。由于我们的环境有log4j包,所以在我们的环境中第一个Log4j的Logger会创建成功。log4j logger创建之后会自动检测,如果没有发现log4j.properties等配置信息就会提示警告。

所以得出一个结论,出现问题是++因为apache-common-logging不会自动发现logback++

问题3,logback和log4j如何桥接

既然apache-common-logging不能发现logback,那log4j和logback如何桥接呢?slf4j官方当然考虑到这个问题,参考官方文档:http://logback.qos.ch/bridge.html
使用log4j-over-slf4j,相当于ACL—>log4j—>slf4j—>logback打通。中间的log4j到slf4j,通过log4j-over-slf4j进行桥接。那么log4j-over-slf4j实际上如何桥接的呢?答案是直接在slf4j中定义同名的log4j的Logger包。这个问题参考:log4j-over-slf4的log4j Loger加载问题

问题4,slf4j存在的价值?

一个很实际的问题是为啥要搞掉JCL? 引入slf4j的成本还是很高的,这篇文章有回答:
http://articles.qos.ch/classloader.html
http://articles.qos.ch/thinkAgain.html

总结,混乱的各种日志组合情况

slf4j-log4j-<version>.jar: 上层是SLF4J,底层通过log4j实现。
slf4j-jcl-<version>.jar: 上层是SLF4J,底层还是通过Commons Logging的动态查找机制。
jcl-over-slf4j-<version>.jar:上层是Commons Logging,底层交给SLF4J提供的静态绑定机制查找真正的日志实现框架。(注意:slf4j-jcl和jcl-over-slf4j不能同时出现在classpath)
log4j-over-slf4j-<version>.jar: 上层是Log4J,底层交给SLF4J静态绑定要真正实现日志打印的框架。

各种冲突情况总结:
slf4j-log4j,slf4j-simple不能同时出现,两个jar包都有sl4j的StaticLoggerBindder会冲突。
log4j-over-slf4j和log4j不能同时出现,两个jar包都有org.apache.log4j.Logger会冲突。
同理,jcl-over-slf4j和common-loggng不能同时出现。
slf4j-jcl和jcl-over-slf4j不能同时出现,逻辑上进入无限递归。

综上所总结情况,jar包的依赖配置如下组合

想用 log4j 和 logback 共存

  • jcl-over-slf4j-xx.jar
  • commons-logging (spring 和 struts2 框架要用到)
  • logback-classic-x.xjar
  • logback-core-xx.jar
  • slf4j-api-xx.jar
  • log4j-over-slf4j-xx.jar

如全新项目,则推荐只用 logback, 只需以下 jar :

  • logback-classic-1.0.0.jar
  • logback-core-1.0.0.jar
  • slf4j-api-1.6.4.jar
  • jcl-over-slf4j-1.6.4.jar
  • commons-logging ( spring 和 struts2 框架要用到)

如果用 slf4j + log4j 则以下 jar:

  • slf4j-api-1.6.4.jar
  • slf4j-logj12.jar ( slf4j 与 log4j 之间的绑定)
  • log4j.jar

附一个log4j.properties 文件可以用 PropertiesTranslator 转换成 logback.xml 文件内容。