3.源码分析
3.1 LogManager
log4j需要通过对配置文件的解析后,进行不同级别信息输出。 org.apache.log4j.LogManager就是负责环境变量的初始化。
LogManager类中所有的方法均为静态的,类中有一个static{}静态代码块,会构造一个debug级别的rootLogger,然后检查log4j是否覆盖了系统属性。如果没有覆盖的话,log4j首先会尝试加载log4j.xml文件。在没有加载成功后,会加载log4j.properties文件。log4j的jar包中没有默认的配置文件,所以当两者都没有加载成功后,会报Could not find resource错。
如果加载文件成功后,会调用OptionConverter类中的selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy)方法。如果是加载xml文件成功,会通过反射实例化DOMConfigurator类。如果是properties文件,会直接创建PropertyConfigurator对象。最后调用Configurator接口中的doConfigure()方法进行文件的解析。
OptionConverter是log4j将配置文件解析成内部制定类型的类。其中的方法有:
concatanateArrays(String[], String[]);
convertSpecialChars(String);
findAndSubst(String, Properties);
getSystemProperty(String, String);
instantiateByKey(Properties, String, Class, Object);
instantiateByClassName(String, Class, Object);
selectAndConfigure(URL, String, LoggerRepository);
substVars(String, Properties);
其中,这里我们用到的是instantiateByClassName,selectAndConfigure和findAndSubst。
instantiateByClassName是在通过反射实例化对象的时候调用,而findAndSubst是在解析配置文件时,判断是否存在指定键值的。
3.2 LoggerRepository
我们看一下,这里LoggerRepository的出现原因和作用。
LoggerRepository是一个接口。提供了很多方法,包括获得rootLogger的Logger.getRootLogger()方法,和其它Logger的 Logger.getLogger(String name)方法。Hierarchy和NOPLoggerRepository分别实现了LoggerRepository接口
Hierarchy真正实现了LoggerRepository的所有方法,并维护了logger的层次结构,我们每getLogger()一下,就会将其本身放入到内置的一个hashtable中。而NOPLoggerRepository只是一个空实现,当重新加载类时,出现错误时创建。
3.3 Logger
配置文件我们已经解析完了,看一下我们如果使用的log4j。我们在用log4j的时候,是这样使用的 Logger logger = LoggerFactory.getLogger(Test.class);logger.debug("hello world");那么这里log4j是在做什么呢?
logger的debug,info,error等这些方法,其实是Logger继承自Category的。Category类中的Level类定义了这些输出级别。当调用logger.debug()方法时,会检查当前Category的输出级别是否为Level.DEBUG_INT,如果是的话,会将消息传递并创建LoggingEvent对象,然后检查是否有Appender,如果没有会发出警告消息。在Category类中的callAppenders方法中,会进一步调用Appender类中的doAppend方法,根据配置文件的Appender类型,选择不同的类,向不同地方打印后者写入消息。Logger在log4j内部存在一个父子的层次关系,也就是说每个Logger对象中有一个parent属性指向其父Logger,log4j这样定义Logger的层次关系: 系统初始化时会产生一个 root Logger(为debug级别),所以以后在程序中调用 Logger.getLogger(..)方法得到的Logger都是间接或直接成为root Logger 的子Logger。也就是在程序中调用 logger.debug("hello") 或其它级别的方法时,log4j会向上一级访问 logger及logger的父logger
3.4 Category
Category类中的Level定义了输出级别。Category本身实现了AppenderAttachable接口。我们查看AppenderAttachable层次结构图可以发现,
AppenderAttachableImpl和Category分别实现了AppenderAttachable,并且仔细看Category中的代码发现,AppenderAttachableImpl aai被当做属性在Category类中。所以会发现,Category的addAppender实际是new了一个AppenderAttachableImpl,将Appender的管理给了aai,在AppenderAttachableImpl有一个Vector,可以保存多个appender,也就是可以将消息输出到多个目的地的原因。
3.5 LoggingEvent
LoggingEvent每次实例化的时候都会创建一个时间。每次我们logger.debug,info,error,fatal,warn时,都会由Logger 产生一个LoggingEvent将其转交给相应的 Appender。那么我们可以看出,LoggingEvent贯穿着整个log4j系统。
3.6 Appender
上面提到了这个Appender。其实Appender是一个接口。
可以看一下,它的层次结构
如果我们在配置文件中配置appender为FileAppender的话,会调用WriterAppender的subAppend方法,调用Layout的format方法对信息进行格式化,最将信息输出到最后的target。
3.6 Layout
消息格式的输出。
通过对配置文件的解析,在format方法将消息转化为需要的格式后返回。