无法打出log4j日志的问题排查

最近在做xu发现了一个问题,本地新配置的log4j文件日志信息没有打印出来,而daily环境却打印了。在排查问题过程中,系统的学习了一下java日志系统,终于把问题解决了。

 

应用使用了log4j,log4jjava界最流行的日志系统之一。业界的日志系统除了log4j外,还有jdk自带的java.util.logginglogbackaavalon-Logkit等。为了使应用能够在不修改代码的前提下平滑迁移日志系统实现,出现了两个开源项目把应用和具体的日志系统实现解耦,分别是simple log facade for java(slf4j)common-logging(JCL)。应用代码只需要依赖这两个包提供的接口,调用接口方法,无需关心具体实现,就能做到日志系统的可配置、可迁移(这边是门面模式的经典场景)。

 

1 对于JCL

package com.longji;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.junit.Test;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class LogTest extends BaseTestCase {

       @Test

       public void testJCL() {

              Log logger = LogFactory.getLog("2");

              logger.error("testJCL");

       }

}

org.apache.commons.logging.LogFactory是个抽象类,LogFactory会根据特定的查找规则去实例化具体的日志实现LogFactory,可能是jdk-loging LogFactory,也可能是log4j LogFactory。再由LogFactory去创建具体的logger实例。

以下是查找规则:

a. 从系统属性中查找键为 org.apache.commons.logging.LogFactory 的值作为 LogFactory 的实现类。

 

b.  使用 JDK1.3 jar Service Provider Interface(SPI) 类发现机制,从配置文件 META-INF/services/org.apache.commons.logging.LogFactory 的的第一行读取 LogFactory 的实现类名。这个 META-INF/services/org.apache.commons.logging.LogFactory 文件可以是某个 Web 应用的根目录中;也可以在 classpath 下的某个jar包中的META-INF/services/org.apache.commons.logging.LogFactory

 

c.   Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 属性值作为 LogFactory 实现类

d.
如果前三步还未找到,或者抛异常,那么就用JCL的默认LogFactory实现org.apache.commons.logging.impl.LogFactoryImpl 

 

2 对于SLF4J

package com.longji;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.junit.Test;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class LogTest extends BaseTestCase {

 

       @Test

       public void testSLF() {

              Logger logger = LoggerFactory.getLogger("1");

              logger.error("testSLF");

       }

 

 

}

org.slf4j.LoggerFactoryJCL类似,也是先按照某种规则去绑定具体的LoggerFactory,它的绑定方式是基于classpath中是否包含某个适配jar包。具体绑定规则如下图。

<!--[endif]-->

应用要先依赖slf4j-api,这个api主要是定义logger的门面接口,要接入某种具体日志系统还需要依赖一个slf4j-XXX.jar,这里面包含了具体日志系统的适配器和绑定器。每个slf4j-XXX.jar中都会有一个绑定器org.slf4j.impl.StaticLoggerBinder,由这个绑定器定义实例化哪个LoggerFactoryLoggerFactory决定了实例化哪种logger。也就是说classloader先加载了哪个绑定器就决定了用哪个日志系统。

 

今年来SLF4J发展迅猛,越来越多的应用开始使用SLF4J来整合日志系统,包括某些原来用JCL的项目也有迁移到SLF4J的需求。SLF4J大有集大成者的风范,于是开发了一些桥接包,jcl-over-slf4j可以把JCL的调用重定向到SLF4J。(下图同时包括了其他日志系统的桥接包jul-to-slf4j,log4j-over-slf4j

<!--[endif]-->

jcl-over-slf4j的桥接实现是利用了JCL创建LoggerFactory的第二条规则,基于Service Provider Interface(SPI) 类发现机制,在jcl-over-slf4j /META-INF/services/org.apache.commons.logging.LogFactory文件中定义了LoggerFactory的实现为SLF4JLogFactory,由SLF4JLogFactory去执行SLF4J的日志系统绑定过程。

 

把上面理清楚之后,我遇到的问题就迎刃而解了。试用一直都是使用JCL+log4j,日志功能一直都很ok。直到某一天使用了eagle wing,它同时依赖了logback-classiclogback-coreslf4j-apijcl-over-slf4j。当它依赖了jcl-over-slf4j之后,悲剧就可能发生,因为对于JCL的调用都被重定向到slf4j,具体的日志系统则是由slf4j的绑定规则实现。从slf4j的绑定规则可以知道,当classpath中只有logback-classicslf4j-log4j12slf4j-jdk14的其中一种时,绑定的结果很明确。但是当出现两个以上时,就会出现多种可能性,取决于classloader先加载到了哪个StaticLoggerBinder

刚好系统jar包依赖里同时出现了logback-classiclogback-core slf4j-log4j12jcl-over-slf4jslf4j-api等,也就导致了我本机打不出logdaily打出log的现象,因为我本机碰巧绑定了logbacklog4j.xml配置的信息无效;而daily刚好绑定了slf4j-log4j12,顺利的打出了log4jlog。

解决的办法就是把eagle wing对logback的依赖exclude掉,这样就保证了slf4j能绑定到log4j。

猜你喜欢

转载自hill007299.iteye.com/blog/1563482