Log4J源码分析

来源:互联网 发布:华为双卡默认移动数据 编辑:程序博客网 时间:2024/05/16 16:00

http://jmut.bokee.com/2410846.html

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

  AppenderAttachableImpl是什么?显然,从其名字可知,它是AppenderAttachable接口的一个实现。查看它的源码,它拥有一个私有域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
来看CategoryaddAppender方法:

/**
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是什么?仓库,听起来像是个工厂,看看它的代码。repositoryLoggerRepository接口类型,而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的确是做为局部容器在用,它只管理当前CategoryAppender
第二,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类。

TagLog4J                                          

上回说到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 thelog4j.defaultInitOverride system property to any other value then "false" will cause log4j to skip the default initialization procedure (this procedure).

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

3. Attempt to convert theresource variable to a URL.

4. If the resource variable cannot be converted to a URL, for example due to aMalformedURLException, then search for theresource from the classpath by callingorg.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 theDOMConfigurator will be used. You can optionaly specify a custom configurator. The value of thelog4j.configuratorClass system property is taken as the fully qualified class name of your custom configurator. The custom configurator you specifymust implement theConfigurator 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+"].");
}

我们又发现了三个重要角色HierarchyOptionConverterLoader。我们先说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 the
hierarchy 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.rootLoggerlog4j.rootCategory设置根配置。方法:

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

处理设置语法:

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

root loggerlevel不能是inheritednull。方法:

Appender parseAppender(Properties props, String appenderName)

设置各个appender,它使用域

protected Hashtable registry = new Hashtable(11);

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

原创粉丝点击