本文主要讲述一个日志分级应用的模型和如何利以及扩展Log4J来达到目的,关于Log4J的配置说明和基础应用就不再累述,详情可参见官方文档http://logging.apache.org/log4j/1.2/manual.html
另外3篇比较详细的博文:
- 入门http://www.blogjava.net/rickhunter/articles/28133.html
- 详细配置http://kdboy.iteye.com/blog/208851
- 代码分析http://heavyz.sourceforge.net/homepage/homepage_zh/comp/notes/log4j.html
首先我们来看一个模型:
Kernel和 Applications都运行于同一个JVM中,且Applications运行于Kernel之上。JVM启动时依次会创建Bootstrap ClassLoader, Extension ClassLoader和AppClassLoader,分别会去加载JRE/lib,JRE/lib/ext下的核心包和kernel以及相关依赖的classes,jars等,之后由kernel创建并加载各个application,整个过程和一般的java web服务器启动比较类似(如tomcat)。
自然而然就有了这样的日志分级需求:
- Kernel的日志记录到kernel.log文件中
- 各个Application的日志分别记录到各自的文件中,如BBS的日志记录到bbs.log;Mails的日志记录到mails.log文件中
- 其它日志(所有非kernel和application中所关心的日志)记录到root.log中,以备不时之需
那么接下我们就要考虑log4j的配置问题了?——在这里我使用的配置文件是log4j.properties
- log4j.properties文件的位置?
- log4j.properties文件的内容?
对于问题1,从2个方面考虑:
- kernel中有使用log4j,并且可以kernel独立,所以log4j.properties需要在kernel中存在
- kernel并不知将来有多少applications,所以关于application日志的配置信息可以:
- 采用默认机制创建,即在kernel加载application时创建
- 在application中也加入log4j的配置信息,加载时读取
此外我们已经分析了整个模型的类加载机制,很显然log4j.jar也只会被kernel加载一次。看过log4j(1.2.16)源代码的同学都知道log4j的初始化是在静态块中进行的,也就是说log4j.properties只会被加载一次,那么对于application中的配置文件就需要我们自己用log4j提供的API在kernel加载application时进行初始化了。
有了问题1的分析,我们可以把模型稍微简化成下图后再来分析问题2
依然是要求app1包下的日志写到app1.log文件中,app2包下的日志写到app2.log文件中,kernel包下的日志写到kernel.log文件中,其余的日志则写到root.log文件中。
log4.properties则如下所示
运行得到了以下日志输出:
不同package下的日志输出到了不同的日志文件中,看起来目的达到了,不过还是存在问题——即便root的log级别设置为了error,但kernel和application中的info信息还是会被输出到root.log中。大家可以想一下这是为什么?
如何避免写到kernel或application中的日志重复被写到root中呢?——关键是org.apache.log4j.Category中的方法callAppenders
/**
Call the appenders in the hierrachy starting at
<code>this</code>. If no appenders could be found, emit a
warning.
<p>This method calls all the appenders inherited from the
hierarchy circumventing any evaluation of whether to log or not
to log the particular log request.
@param event the event to log. */
public
void callAppenders(LoggingEvent event) {
int writes = 0;
for(Category c = this; c != null; c=c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized(c) {
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
我们最终的目的就是阻止其输出日志后还继续上溯输出,因此需做如下修改:
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
break;
}
除了修改log4j其原生代码外,我们还可以通过继承和扩展的手段实现,下面便是所有实现代码。
- 扩展org.apache.log4j.Logger和DefaultLoggerFactory如下
package com.xxx.cases
public class DefaultLoggerFactory implements org.apache.log4j.spi.LoggerFactory {
public DefaultLoggerFactory() {
}
public Logger makeNewLoggerInstance(String name) {
return new Logger(name);
}
}
package com.xxx.cases;
import org.apache.log4j.Appender;
import org.apache.log4j.helpers.AppenderAttachableImpl;
import org.apache.log4j.spi.LoggingEvent;
public class Logger extends org.apache.log4j.Logger{
private static final DefaultLoggerFactory defaultFactory = new DefaultLoggerFactory();
private AppenderAttachableImpl aai;
protected Logger(String name) {
super(name);
}
@Override
public void callAppenders(LoggingEvent event) {
if (this.aai == null && !(this.getParent() instanceof Logger)) {
super.callAppenders(event);
} else {
int writes = 0;
for (Logger c = this; c != null; c = (Logger) c.getParent()) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized (c) {
if (c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
break;
}
if (!c.getAdditivity()) {
break;
}
}
}
if (writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
}
public synchronized void addAppender(Appender newAppender) {
if (aai == null) {
aai = new AppenderAttachableImpl();
}
aai.addAppender(newAppender);
repository.fireAddAppenderEvent(this, newAppender);
}
public static Logger getLogger(Class clazz){
return (Logger)org.apache.log4j.Logger.getLogger(clazz.getName(), defaultFactory);
}
}
- 修改log4j.properties配置文件,增加loggerFactory配置项
- 新Logger的应用(如在kernel中)
package com.xxx.cases.kernel;
import com.xxx.cases.Logger;
public class KernelBean {
private static Logger logger = Logger.getLogger(KernelBean.class);
public void testMethod(){
logger.info("in kernel bean test method");
}
}
后记:经朋友提醒,还有一种更加简便的方式,只需要修改log4.properties配置信息——其目的和原理是一样的。
注意黄色高亮的部分。
——结束