最近闲来无事,正好手头上有Log4j的代码,于是就拿来学习了下。 想来这个小工具也用了很多年了,但是从来没有真正的去了解过内部机制,经过一番学习后,发现结构还是蛮不错的,里面有很多东西值得学习。
总的来说,Log4j的代码我认为可以分为这几大部分:
1. 产品Logger
2. 附件Appender
3. 仓储Repository
4. 生产工厂Factory
5. 配置管理Configuration
产品Logger
Log4j里面最重要的当然就是Logger类了,在代码中,我们通常采用LogManager.getLogger(clazz);或者Logger.getLogger(clazz) 方法来获取Logger对象。我们就来看看这里面到底有什么玄机。
下图其实是简略的版本,将一些重要的类放在其它的类图中,以突出Logger和Appender之间的关系。
Log4j初始的加载代码其实是在LogManager的静态初始块中,我会在另一篇中提到。但首先,我们先来看看LogManager.getLogger(clazz);和Logger.getLogger(clazz) 到底有什么区别:
public class Logger extends Category {
static public Logger getLogger(String name) {
return LogManager.getLogger(name);
}
...
}
可以看出,Logger本身其实并不做任何生成Logger实例的事情,而是直接代理给LogManager的静态方法。我们在代码中使用任何一种方式都是一样的。
如果仔细浏览Logger类里的代码,就会发现,Logger本身几乎不做任何事情,只有几个简单的trace方法将消息以trace的级别来记录。而我们经常使用的方法像info(), error()等,都不在Logger类里。相应的,其实他们都可以在Logger的父类Category里面找到。
按照Javadoc,Category类是deprecated and replaced by the subclass Logger,也就是说,在早期的版本中,可能用的都是Category而不是Logger类。而我们常用的一些方法,其实也还是用的是Category类里面的。
Category里面有几个很重要的变量:
volatile protected Level level;
volatile protected Category parent;
protected LoggerRepository repository;
AppenderAttachableImpl aai;
Repository, 每个category都属于某个repository,我们会在下一个章节详细描述LoggerRepository类。
level,每一个Category(Logger)都拥有一个级别,这个级别就是我们通常所说的DEBUG,WARN,ERROR这些。这里就不再展开描述了。
parent,parent本身又是一个Category,这里就涉及到一个很重要的设计,整个Categeory是一个级联的结构,当我们对category实例执行某项log操作时,它所有的父节点也会相应的执行同样的操作:
public void info(Object message) {
if(repository.isDisabled(Level.INFO_INT)) return;
if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel()))
forcedLog(FQCN, Level.INFO, message, null);
}
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
callAppenders(new LoggingEvent(fqcn, this, level, message, t));
}
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);
}
}
从红色文字上可以看出,当Category有父亲时,将会首先调用自身的log记录信息,然后调用父亲的方法,直到继承的顶端。
下图展示了当用户调用info方法时的序列图:
从上图及代码可以发现,Category本身并没有做任何的记录log的工作,所有的这些工作都是由AppenderAttachableImpl来完成的。这个类会在下一篇文章里详细介绍。
至此,关于Logger类的内容已经大致描述,大部分时候,Logger并不做任何事情,而是将工作代理给其它类,但Logger仍然是整个Log4j里最重要的类,同时也是学习Log4j的最佳入口。