Log4J源码分析
http://jmut.bokee.com/2410846.html
Log4J源碼分析(一)- -
Tag: Log4J ?? ??????????????????????????????????????
這幾天學(xué)用了Log4J,覺得確實是很好的東西,而且發(fā)現(xiàn)它的功能有很多實現(xiàn),都是一直想知道了,看看了看它的源碼量也不是很大,所以決定讀一下它的源碼,為了映象深刻,所以寫下這個源碼分析。
雖然Log4J1.2.11的源碼量只有1.05M,粗略估算一下大概有18350行代碼:
面對這近20000行代碼,188個類,還真是有點不知從何下手。萬事開頭難嘛!既然使用Log4J是從Category類開始的,那也從它開始分析。
Category類位于org.apache.log4j包內(nèi),檢查它的類層次圖,它實現(xiàn)了AppenderAttachable接口。
AppenderAttachable接口同樣位于org.apache.log4j包內(nèi),查看它的源碼,很明顯,它可以被視為一個Appender的容器,所以Category類也可以當(dāng)作是Appender的容器。
但是,事情沒這么簡單,在Category類中有個私有域:
AppenderAttachableImpl是什么?顯然,從其名字可知,它是AppenderAttachable接口的一個實現(xiàn)。查看它的源碼,它擁有一個私有域Vector appenderList,并在其上實現(xiàn)了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;
}
從這個方法的實現(xiàn)來看,它像是觀察者(Observer)模式中的Notify方法。通過觀察Appender的實現(xiàn),可以肯定在這里使用了這模式,Appender做為AppenderAttachableImpl的觀察者。
?
接著來。
昨天說過,Category實現(xiàn)了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是有標(biāo)識的,由方法:
public
abstract
void fireAddAppenderEvent(Category logger, Appender appender);
的簽名可以看出這個標(biāo)識是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的確是做為局部容器在用,它只管理當(dāng)前Category的Appender。
第二,additive決定是否要回溯,它控制是否要使用父類的Appender。
第三,對Category進行同步訪問,因為Category是可以在多線程環(huán)境中使用的。
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.
}
從當(dāng)前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ü */
可見這個類可能是負(fù)責(zé)全局Logger管理。這個類不大,但感覺它是很重要的一個類,所以有必要深入研究一下。既然在注釋中提到了手冊中有關(guān)于默認(rèn)初始化過程的描述,那就先看看它是怎么說的。
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系統(tǒng)變量的值,false將跳過默認(rèn)初始化過程。
·? 2、讀取log4j.configuration系統(tǒng)變量的值,生成配置文件url,默認(rèn)值為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存在,則繼續(xù)用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+"].");
}
我們又發(fā)現(xiàn)了三個重要角色Hierarchy、OptionConverter和Loader。我們先說OptionConverter,顯然它是一個工具類,提供一些基礎(chǔ)服務(wù)。我們現(xiàn)在用到的方法是:
/**
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);
}
該方法先構(gòu)造正確的Configurator,再進行配置。有兩個默認(rèn)的Configurator,一個是DOMConfigurator,一個是PropertyConfigurator是。在沒有指定Configurator時,如果配置文件是以.xml結(jié)尾,則使用DOMConfigurator,否則使用PropertyConfigurator。http://jmut.bokee.com/2451816.html
現(xiàn)在初始化進入到了最重要的階段。我們先看看最常用的PropertyConfigurator,它從一個外部文件中讀取配置。這個類最重要的方法當(dāng)然是幾個doConfigure方法。其中最復(fù)雜的是:
public
void doConfigure(Properties properties, LoggerRepository hierarchy)
它是接口Configurator中的方法。
1、由鍵log4j.debug設(shè)定內(nèi)部debug信息是否輸出;
2、由鍵log4j.thredsold設(shè)定全局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設(shè)置根配置。方法:
void parseCategory(Properties props, Logger logger, String optionKey,
String loggerName, String value)
處理設(shè)置語法:
log4j.rootLogger=[level], appenderName, appenderName, ...
root logger的level不能是inherited或null。方法:
Appender parseAppender(Properties props, String appenderName)
設(shè)置各個appender,它使用域
protected Hashtable registry = new Hashtable(11);
跟蹤當(dāng)前文件中的appender。在這個方法中使用了工具類PropertySetter。
4、由鍵log4j.loggerFactory配置LoggerFactory。同樣使用了工具類PropertySetter。
5、由log4j.logger或log4j.category配置非根logger。
6、清空registry。
PropertySetter是一個重要的類,它通過JavaBean使用了reflection。它不設(shè)置appender的layout。
至此,初始化工作完成。整個初始化是圍繞一個配置文件來做的,但中心是由這個文件得來的Properties對象,所以這個文件可以有多種形式,甚至可以是其它的載體,不必是文件,關(guān)鍵是能得到一個Properties對象。然后根據(jù)Properties對象中的信息生成、配置和組織logger。由于支持使用變量,所以在由key獲取value時,要用到工具方法,實現(xiàn)變量的替換。現(xiàn)在生成和配置的問題已經(jīng)解決,剩下的是logger的組織,它是在Hierarchy中解決的。
總結(jié)
- 上一篇: JMS中queue和topic区别
- 下一篇: Logger.getLogger和 Lo