ls4j初始化过程参考:logback如何发现configuration file
今天使用logback遇到一个问题:NND已经使用了logback.xml但log4j还是会提示警告
2013-12-10 17:48:56 [INFO] com.taobao.hsf.tlog.proxy.gather.impl.ProxyAgentGather - ProxyAgentGather process jobs : 0, time cost : 0 ms log4j:WARN No appenders could be found for logger (org.apache.commons.httpclient.HttpClient). log4j:WARN Please initialize the log4j system properly.
我的logback配置
<?xml version="1.0" encoding="UTF-8"?> <!-- Logback Configuration. --> <configuration debug="false"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%p] %c - %m%n</pattern> </encoder> </appender> <root> <level value="INFO" /> <appender-ref ref="STDOUT" /> </root> </configuration>
我完美的想法是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?
我就不翻译了,请看官方代码注释,5种情况写得很清楚明白:
Construct and return a LogFactory instance, using the following ordered lookup procedure to determine the name of the implementation class to be loaded.
1.The org.apache.commons.logging.LogFactory system property.
2.The JDK 1.3 Service Discovery mechanism
3.Use the properties file commons-logging.properties file, if found in the class path of this class. The configuration file is in standard
4.java.util.Properties format and contains the fully qualified name of the implementation class with the key being the system property defined above.(org.apache.commons.logging.LogFactory)
5.Fall back to a default implementation class (org.apache.commons.logging.impl.LogFactoryImpl).
NOTE - If the properties file method of identifying the LogFactory implementation class is utilized, all of the properties defined in this file will be set as configuration attributes on the corresponding LogFactory instance.
NOTE - In a multithreaded environment it is possible that two different instances will be returned for the same classloader environment.
在我的情况下会执行到最后一种情况,也就是使用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是一个代理类,其构造函数如下:
public Log4JLogger(String name) { this.name = name; this.logger = getLogger(); } public Logger getLogger() { if (logger == null) { logger = Logger.getLogger(name); } return (this.logger); }可以看到实际使用的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不能同时出现,逻辑上进入无限递归。
各种日志方案
1.使用slf4j-log4j12+log4j
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
ps:不用依赖slf4j-api, slf4j-log4j12会自动依赖slf4j-api。
2.使用log4j-over-slf4j+logback
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.11</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.4</version> </dependency>
ps:不用依赖slf4j-api, logback-classic和log4j-over-slf4j都会自动依赖slf4j-api。
[INFO] +- ch.qos.logback:logback-classic:jar:1.0.11:compile
[INFO] | +- ch.qos.logback:logback-core:jar:1.0.11:compile
[INFO] | \- org.slf4j:slf4j-api:jar:1.6.4:compile (version managed from 1.7.4)
[INFO] +- org.slf4j:log4j-over-slf4j:jar:1.7.4:compile
[INFO] | \- org.slf4j:slf4j-api:jar:1.6.4:compile (version managed from 1.7.4)
log4j的体系结构
log4j主要由logger,appender,layout构成:
appender在log系统中的含义是定义输出日志的策略,常用的包括:
- ConsoleAppender
- DailyRollingFileAppender
- RollingFileAppender
- RandomAccessFileAppender(FastFileAppender)
关于log4j的架构可以参考:http://logging.apache.org/log4j/2.x/manual/architecture.html
配置可以参考:http://logging.apache.org/log4j/1.2/manual.html#Configuration
参考
桥接方案可以参考这篇文章:http://ihyperwin.iteye.com/blog/1455596