Log4J源码分析(一)- -

Tag Log4J                                          

  这几天学用了Log4J,觉得确实是很好的东西,而且发现它的功能有很多实现,都是一直想知道了,看看了看它的源码量也不是很大,所以决定读一下它的源码,为了映象深刻,所以写下这个源码分析。

  虽然Log4J1.2.11的源码量只有1.05M,粗略估算一下大概有18350行代码:






面对这近20000行代码,188个类,还真是有点不知从何下手。万事开头难嘛!既然使用Log4J是从Category类开始的,那也从它开始分析。
  Category类位于org.apache.log4j包内,检查它的类层次图,它实现了AppenderAttachable接口。
  AppenderAttachable接口同样位于org.apache.log4j包内,查看它的源码,很明显,它可以被视为一个Appender的容器,所以Category类也可以当作是Appender的容器。
  但是,事情没这么简单,在Category类中有个私有域:


AppenderAttachableImpl aai


Vector appenderList,并在其上实现了AppenderAttachalbe接口。此外,AppenderAttachableImpl还添加了一个重要的方法:


/**  
Call the   doAppend   method on all attached appenders. */ 
 
public  
int appendLoopOnAppenders(LoggingEvent event) {  
 int size = 0;  
 Appender appender;  

 if(appenderList != null) {  
  size = appenderList.size();  
  for(int i = 0; i < size; i++) {  
   appender = (Appender) appenderList.elementAt(i);  
   appender.doAppend(event);  
  }  
 }   
 return size;  
}


从这个方法的实现来看,它像是观察者(Observer)模式中的Notify方法。通过观察Appender的实现,可以肯定在这里使用了这模式,Appender做为AppenderAttachableImpl的观察者。

 

接着来。
昨天说过,Category实现了AppenderAttachable接口,可以视为是Appender的容器,但是它又有私有域aai,且aai才是真正的Appender容器在,所以Category的容器方法是对aai上方法的包装,Category把对Appender的管理委托给了aai。
来看Category的addAppender方法:

/**
 Add newAppender to the list of appenders of this
 Category instance.If newAppender is already in the list of
appenders, then it won't be added again.*/
synchronized
 public
 void addAppender(Appender newAppender) {
 if(aai == null) {
  aai = new AppenderAttachableImpl();
 }
 aai.addAppender(newAppender);
 repository.fireAddAppenderEvent(this, newAppender);
 }

明显,Category确实是委托了对Appender的管理给aai,但是

repository.fireAddAppenderEvent(this, newAppender);

在做什么呢?repository是什么?仓库,听起来像是个工厂,看看它的代码。repository是LoggerRepository接口类型,而LoggerRepository接口的注释说:

/**
 A LoggerRepository is used to create and retrieve
Loggers. The relation between loggers in a repository
 depends on the repository but typically loggers are arranged in a
 named hierarchy.In addition to the creational methods, a
LoggerRepository can be queried for existing loggers,
 can act as a point of registry for events related to loggers.@author Ceki Gülcü
 @since 1.2 */

再由接口中的方法,可以看出这个接口也可以看作是一种容器,在这里面的Appender是有标识的,由方法:

public
abstract
void fireAddAppenderEvent(Category logger, Appender appender);

的签名可以看出这个标识是Category。
到这里,可以说在Category中至少有两个容器在管理Appender存储,aai上没有组织,repository里的Appender是有组织的。
猜想,aai是局部的容器,repository是全局的容器。接下来分析Category的方法callAppenders:

/**
 Call the appenders in the hierrachy starting at
this. If no appenders could be found, emit a
 warning.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);
 }
 }这里面有几点要说。
第一,由c.aai.appendLoopOnAppenders(event)看出aai的确是做为局部容器在用,它只管理当前Category的Appender。
第二,additive决定是否要回溯,它控制是否要使用父类的Appender。
第三,对Category进行同步访问,因为Category是可以在多线程环境中使用的。
 Category的方法:/**
 Starting from this category, search the category hierarchy for a
 non-null level and return it. Otherwise, return the level of the
 root category.The Category class is designed so that this method executes as
quickly as possible.*/
public
 Level getEffectiveLevel() {
 for(Category c = this; c != null; c=c.parent) {
 if(c.level != null)
 return c.level;
 }
 return null; // If reached will cause an NullPointerException.
 }从当前Category开始,向上寻找第一个有效的Level。
 Category的使用入口是getInstance:/**
 * @deprecated Make sure to use {@link Logger#getLogger(String)} instead.
 */
public
 static
 Category getInstance(String name) {
 return LogManager.getLogger(name);
 }

/**
 * @deprecated Please make sure to use {@link Logger#getLogger(Class)} 
 * instead.
 */ 
public
 static
 Category getInstance(Class clazz) {
 return LogManager.getLogger(clazz);
 }它们把工作交给了LogManger类。
Tag:Log4J                                           
上回说到LogManager,接下来就分析它。
先看它的注释:/**
 * Use the LogManager class to retreive {@link Logger}
 * instances or to operate on the current {@link
 * LoggerRepository}. When the LogManager class is loaded
 * into memory the default initalzation procedure is inititated. The
 * default intialization procedure is described in the 
 * href="../../../../manual.html#defaultInit">short log4j manual.
 *
 * @author Ceki Gülcü */

可见这个类可能是负责全局Logger管理。这个类不大,但感觉它是很重要的一个类,所以有必要深入研究一下。既然在注释中提到了手册中有关于默认初始化过程的描述,那就先看看它是怎么说的。

The exact default initialization algorithm is defined as follows:

1. Setting the log4j.defaultInitOverride system property to any other value then "false" will cause log4j to skip the default initialization procedure (this procedure).

2. Set the resource string variable to the value of the log4j.configuration system property. The preferred way to specify the default initialization file is through the log4j.configuration system property. In case the system property log4j.configuration is not defined, then set the string variable resource to its default value "log4j.properties".

3. Attempt to convert the resource variable to a URL.

4. If the resource variable cannot be converted to a URL, for example due to a MalformedURLException, then search for the resource from the classpath by calling org.apache.log4j.helpers.Loader.getResource(resource, Logger.class) which returns a URL. Note that the string "log4j.properties" constitutes a malformed URL.

See

Loader.getResource(java.lang.String) for the list of searched locations.

5. If no URL could not be found, abort default initialization. Otherwise, configure log4j from the URL.

The PropertyConfigurator will be used to parse the URL to configure log4j unless the URL ends with the ".xml" extension, in which case the DOMConfigurator will be used. You can optionaly specify a custom configurator. The value of the log4j.configuratorClass system property is taken as the fully qualified class name of your custom configurator. The custom configurator you specify must implement the Configurator interface.

以上是就是整个初始化过程。其源码如下:

static {
// By default we use a DefaultRepositorySelector which always returns 'h'.
 Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
 repositorySelector = new DefaultRepositorySelector(h);

/** Search for the properties file log4j.properties in the CLASSPATH. */
 String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
 null);

// if there is no default init override, then get the resource
 // specified by the user or the default config file.
 if(override == null || "false".equalsIgnoreCase(override)) {

 String configurationOptionStr = OptionConverter.getSystemProperty(
 DEFAULT_CONFIGURATION_KEY,
 null);

 String configuratorClassName = OptionConverter.getSystemProperty(
 CONFIGURATOR_CLASS_KEY,
 null);

 URL url = null;

// if the user has not specified the log4j.configuration
 // property, we search first for the file "log4j.xml" and then
 // "log4j.properties"
 if(configurationOptionStr == null) {
 url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
 if(url == null) {
 url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
 }
 } else {
 try {
 url = new URL(configurationOptionStr);
 } catch (MalformedURLException ex) {
// so, resource is not a URL:
 // attempt to get the resource from the class path
 url = Loader.getResource(configurationOptionStr);
 }
 }

// If we have a non-null url, then delegate the rest of the
 // configuration to the OptionConverter.selectAndConfigure
 // method.
 if(url != null) {
 LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
 OptionConverter.selectAndConfigure(url, configuratorClassName,
 LogManager.getLoggerRepository());
 } else {
 LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
 }
 }·  1、检查log4j.defaultInitOverride系统变量的值,false将跳过默认初始化过程。
·  2、读取log4j.configuration系统变量的值,生成配置文件url,默认值为log4j.properties或者log4j.xml。
// if the user has not specified the log4j.configuration
 // property, we search first for the file "log4j.xml" and then
 // "log4j.properties"
if(configurationOptionStr == null) { 
 url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
 if(url == null) {
 url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
 }
 } else {
 try {
  url = new URL(configurationOptionStr);
 } catch (MalformedURLException ex) {
 // so, resource is not a URL:
 // attempt to get the resource from the class path
 url = Loader.getResource(configurationOptionStr); 
 } 
 }·  3、如果url存在,则继续用OptionConverter初始化。
// If we have a non-null url, then delegate the rest of the
 // configuration to the OptionConverter.selectAndConfigure
 // method.
 if(url != null) {
 LogLog.debug("Using URL ["+url+"] for automatic log4j configuration."); 
 OptionConverter.selectAndConfigure(url, configuratorClassName, 
 LogManager.getLoggerRepository());
 } else {
 LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
 } 我们又发现了三个重要角色Hierarchy、OptionConverter和Loader。我们先说OptionConverter,显然它是一个工具类,提供一些基础服务。我们现在用到的方法是:
/**
 Configure log4j given a URL.The url must point to a file or resource which will be interpreted by
a new instance of a log4j configurator.
 All configurations steps are taken on thehierarchy passed as a parameter.@param url The location of the configuration file or resource.
 @param clazz The classname, of the log4j configurator which will parse
 the file or resource at url. This must be a subclass of
 {@link Configurator}, or null. If this value is null then a default
 configurator of {@link PropertyConfigurator} is used, unless the
 filename pointed to by url ends in '.xml', in which case
 {@link org.apache.log4j.xml.DOMConfigurator} is used.
 @param hierarchy The {@link org.apache.log4j.Hierarchy} to act on.

 @since 1.1.4 */

 static
 public
 void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
 Configurator configurator = null;
 String filename = url.getFile();

 if(clazz == null && filename != null && filename.endsWith(".xml")) {
  clazz = "org.apache.log4j.xml.DOMConfigurator";
 }

 if(clazz != null) {
  LogLog.debug("Preferred configurator class: " + clazz);
  configurator = (Configurator) instantiateByClassName(clazz,
  Configurator.class,null);
  if(configurator == null) {
   LogLog.error("Could not instantiate configurator ["+clazz+"].");
   return;
  }
 } else {
  configurator = new PropertyConfigurator();
 }

 configurator.doConfigure(url, hierarchy);
 }

该方法先构造正确的Configurator,再进行配置。有两个默认的Configurator,一个是DOMConfigurator,一个是PropertyConfigurator是。在没有指定Configurator时,如果配置文件是以.xml结尾,则使用DOMConfigurator,否则使用PropertyConfiguratorhttp://jmut.bokee.com/2451816.html

现在初始化进入到了最重要的阶段。我们先看看最常用的PropertyConfigurator,它从一个外部文件中读取配置。这个类最重要的方法当然是几个doConfigure方法。其中最复杂的是:

public
void doConfigure(Properties properties, LoggerRepository hierarchy)

它是接口Configurator中的方法。
1、由键log4j.debug设定内部debug信息是否输出;
2、由键log4j.thredsold设定全局level,它的value中可以使用变量,所以要进行变量替换,可以参见OptionConverter

public
static
String findAndSubst(String key, Properties props)

public static
String substVars(String val, Properties props) throws
IllegalArgumentException

方法的说明。
3、由键log4j.rootLogger或log4j.rootCategory设置根配置。方法:

void parseCategory(Properties props, Logger logger, String optionKey,
String loggerName, String value)

处理设置语法:

log4j.rootLogger=[level], appenderName, appenderName, ...

root logger的level不能是inherited或null。方法:

Appender parseAppender(Properties props, String appenderName)

设置各个appender,它使用域

protected Hashtable registry = new Hashtable(11);

跟踪当前文件中的appender。在这个方法中使用了工具类PropertySetter
4、由键log4j.loggerFactory配置LoggerFactory。同样使用了工具类PropertySetter
5、由log4j.logger或log4j.category配置非根logger。
6、清空registry。
PropertySetter是一个重要的类,它通过JavaBean使用了reflection。它不设置appender的layout。
至此,初始化工作完成。整个初始化是围绕一个配置文件来做的,但中心是由这个文件得来的Properties对象,所以这个文件可以有多种形式,甚至可以是其它的载体,不必是文件,关键是能得到一个Properties对象。然后根据Properties对象中的信息生成、配置和组织logger。由于支持使用变量,所以在由key获取value时,要用到工具方法,实现变量的替换。现在生成和配置的问题已经解决,剩下的是logger的组织,它是在Hierarchy中解决的。