天天看點

深入Log4J源碼之LoggerRepository和Configurator

loggerrepository從字面上了解,它是一個logger的容器,它會建立并緩存logger執行個體,進而具有相同名字的logger執行個體不會多次建立,以提高性能。它的這種特性有點類似spring的ioc概念。log4j支援兩種配置檔案:properties檔案和xml檔案。configurator解析配置檔案,并将解析後的資訊添加到loggerrepository中。logmanager最終将loggerrepository和configurator整合在一起。

loggerrepository是一個logger的容器,它負責建立、緩存logger執行個體,同時它也維護了logger之間的關系,因為在log4j中,所有logger都組裝成以rootlogger為根的一棵樹,樹的層次由logger的name來決定,其中以’.’分隔。

除了做為一個logger容器,它還有一個threshold屬性,用于過濾所有在threshold級别以下的日志。以及其他和logger操作相關的方法和屬性。

loggerrepository的接口定義如下:

 1 public interface loggerrepository {

 2     public void addhierarchyeventlistener(hierarchyeventlistener listener);

 3     boolean isdisabled(int level);

 4     public void setthreshold(level level);

 5     public void setthreshold(string val);

 6     public void emitnoappenderwarning(category cat);

 7     public level getthreshold();

 8     public logger getlogger(string name);

 9     public logger getlogger(string name, loggerfactory factory);

10     public logger getrootlogger();

11     public abstract logger exists(string name);

12     public abstract void shutdown();

13     public enumeration getcurrentloggers();

14     public abstract void fireaddappenderevent(category logger, appender appender);

15     public abstract void resetconfiguration();

16 }

hierarchy是log4j中預設對loggerrepository的實作類,它用于表達其内部的logger是以層次結構存儲的。在對loggerrepository接口的實作中,getlogger()方法是其最核心的實作,因而首先從這個方法開始。

hierarchy中用一個hashtable來存儲所有logger執行個體,它以categorykey作為key,logger作為value,其中categorykey是對logger中name字元串的封裝,之是以要引入這個類是出于性能考慮,因為它會緩存name字元串的hash code,這樣在查找過程中計算hash code時就可以直接取得而不用每次都計算。

 1 class categorykey {

 2     string name;

 3     int hashcache;

 4 

 5     categorykey(string name) {

 6         this.name = name;

 7         hashcache = name.hashcode();

 8     }

 9     final public int hashcode() {

10         return hashcache;

11     }

12     final public boolean equals(object rarg) {

13         if (this == rarg)

14             return true;

15         if (rarg != null && categorykey.class == rarg.getclass())

16             return name.equals(((categorykey) rarg).name);

17         else

18             return false;

19     }

20 }

getlogger()方法中有一個重載函數提供loggerfactory接口,它用于沒有在loggerrepository中找到logger執行個體時建立相應的logger執行個體,預設實作直接建立一個logger執行個體,使用者可以通過自定義loggerfactory實作建立自己的logger執行個體。

1 public interface loggerfactory {

2     public logger makenewloggerinstance(string name);

3 }

4 class defaultcategoryfactory implements loggerfactory {

5     public logger makenewloggerinstance(string name) {

6         return new logger(name);

7     }

8 }

getlogger()方法首先根據傳入name建立categorykey執行個體,而後從緩存ht字段中查找:

1.       如果找到對應的logger執行個體,則直接傳回該執行個體。

2.       如果沒有找到任何執行個體,則使用loggerfactory建立新的logger執行個體,并将該執行個體緩存到ht集合中,同時更新新建立logger執行個體的parent屬性。更新parent屬性最簡單的做法是從後往前以’.’為分隔符截取字元串,使用截取後的字元串從ht集合中查找是否存在logger執行個體,如果存在,則新建立的logger執行個體的parent即為找到的執行個體,若在整個周遊過程中都沒有找到相應的parent執行個體,則其parent執行個體為root。然而如果一個“x.y.z.w”logger起初的parent設定為root,而後出現“x.y.z”logger執行個體,那麼就需要更新“x.y.z.w”logger的parent為“x.y.z”logger執行個體,此時就會遇到一個如何找到在集合中已經存在的“x.y.z”logger執行個體子節點的問題。當然一種簡單的做法是周遊ht集合中所有執行個體,判斷那個執行個體是不是“x.y.z”logger執行個體的子節點,是則更新其parent節點。由于每次的周遊會引起一些性能問題,因而log4j使用provisionnode事先将所有的可能相關的子節點儲存起來,并将provisionnode執行個體添加到ht集合中,這樣隻要找到對應的provisionnode執行個體,就可以找到所有相關的子節點了。比如對“x.y.z.w”logger執行個體,它會産生三個provisionnode執行個體(當然如果相應的執行個體已經存在,則直接添加而無需建立,另外,如果相應節點已經是logger執行個體,那麼将“x.y.z.w”logger執行個體的parent直接指向它即可):provisionnode(“x”), provisionnode(“x.y”), provisionnode(“x.y.z”),他們都存儲了“x.y.z.w”logger執行個體作為其子節點。

 1 class provisionnode extends vector {

 2     provisionnode(logger logger) {

 3         super();

 4         this.addelement(logger);

 5     }

 6 }

 7 final private void updateparents(logger cat) {

 8     string name = cat.name;

 9     int length = name.length();

10     boolean parentfound = false;

11     // if name = "x.y.z.w", loop thourgh "x.y.z", "x.y" and "x"

12     for (int i = name.lastindexof('.', length - 1); i >= 0; i = name

13             .lastindexof('.', i - 1)) {

14         string substr = name.substring(0, i);

15         categorykey key = new categorykey(substr); 

16         object o = ht.get(key);

17         if (o == null) {

18             provisionnode pn = new provisionnode(cat);

19             ht.put(key, pn);

20         } else if (o instanceof category) {

21             parentfound = true;

22             cat.parent = (category) o;

23             break; // no need to update the ancestors of the closest

24                     // ancestor

25         } else if (o instanceof provisionnode) {

26             ((provisionnode) o).addelement(cat);

27         } else {

28             exception e = new illegalstateexception(

29                     "unexpected object type " + o.getclass() + " in ht.");

30             e.printstacktrace();

31         }

32     }

33     // if we could not find any existing parents, then link with root.

34     if (!parentfound)

35         cat.parent = root;

36 }

3.   如果找到的是provisionnode執行個體,首先使用factory建立新的logger執行個體,将該執行個體添加到ht集合中,然後更新找到的provisionnode内部所有logger的parent字段以及新建立logger的parent字段。更新過程中需要注意provisionnode中的logger執行個體已經指向了正确的parent了,是以隻要更新那些provisionnode中logger執行個體指向的parent比新建立的logger本身層次要高的那些parent屬性。比如開始插入“x.y.z”logger執行個體,而後插入“x.y.z.w”logger執行個體,此時provisionnode(“x”)認為“x.y.z”logger執行個體和“x.y.z.w”logger執行個體都是它的子節點,而後插入“x”logger執行個體,那麼隻需要更新“x.y.z”logger的父節點為“x”logger執行個體即可,而不用更新“x.y.z.w”logger執行個體的父節點。

 1 final private void updatechildren(provisionnode pn, logger logger) {

 2     final int last = pn.size();

 3     for (int i = 0; i < last; i++) {

 4         logger l = (logger) pn.elementat(i);

 5         // unless this child already points to a correct (lower) parent,

 6         // make cat.parent point to l.parent and l.parent to cat.

 7         if (!l.parent.name.startswith(logger.name)) {

 8             logger.parent = l.parent;

 9             l.parent = logger;

10         }

12 }

綜合起來,getlogger()方法的實作代碼如下:

 1 public logger getlogger(string name, loggerfactory factory) {

 2     categorykey key = new categorykey(name);

 3     logger logger;

 4     synchronized (ht) {

 5         object o = ht.get(key);

 6         if (o == null) {

 7             logger = factory.makenewloggerinstance(name);

 8             logger.sethierarchy(this);

 9             ht.put(key, logger);

10             updateparents(logger);

11             return logger;

12         } else if (o instanceof logger) {

13             return (logger) o;

14         } else if (o instanceof provisionnode) {

15             logger = factory.makenewloggerinstance(name);

16             logger.sethierarchy(this);

17             ht.put(key, logger);

18             updatechildren((provisionnode) o, logger);

19             updateparents(logger);

20             return logger;

21         } else {

22             // it should be impossible to arrive here

23             return null; // but let's keep the compiler happy.

24         }

25     }

26 }

其他的方法實作則比較簡單,對loggerrepository來說,它也可以像其注冊hierarchyeventlistener監聽器,每當向一個logger添加或删除appender,該監聽器就會觸發。

 1 public interface hierarchyeventlistener {

 2     public void addappenderevent(category cat, appender appender);

 3     public void removeappenderevent(category cat, appender appender);

 4 }

 5 private vector listeners;

 6 public void addhierarchyeventlistener(hierarchyeventlistener listener) {

 7     if (listeners.contains(listener)) {

 8         loglog.warn("ignoring attempt to add an existent listener.");

 9     } else {

10         listeners.addelement(listener);

13 public void fireaddappenderevent(category logger, appender appender) {

14     if (listeners != null) {

15         int size = listeners.size();

16         hierarchyeventlistener listener;

17         for (int i = 0; i < size; i++) {

18             listener = (hierarchyeventlistener) listeners.elementat(i);

19             listener.addappenderevent(logger, appender);

20         }

21     }

22 }

23 void fireremoveappenderevent(category logger, appender appender) {

24     if (listeners != null) {

25         int size = listeners.size();

26         hierarchyeventlistener listener;

27         for (int i = 0; i < size; i++) {

28             listener = (hierarchyeventlistener) listeners.elementat(i);

29             listener.removeappenderevent(logger, appender);

30         }

31     }

32 }

hierarchy中儲存了threshold字段,使用者可以設定threshold。而對root執行個體,它在夠着hierarchy時就被指定了。getcurrentloggers()方法将ht集合中所有的logger執行個體取出。shutdown()方法周遊所有logger執行個體以及root執行個體,調用所有附加其上的appender的close()方法,并将所有appender執行個體從logger中移除,最後觸發appenderremove事件。resetconfiguration()方法将root字段初始化、調用shutdown()方法移除logger中的所有appender、初始化所有logger執行個體當不将其從loggerrepository中移除、清楚renderermap和throwablerender中的資料。

renderersupport接口支援使用者為不同的類設定相應的objectrender執行個體,進而可以從被渲染的類中或許更多的資訊而不是預設的調用其tostring()方法。

 1 public interface renderersupport {

 2     public renderermap getrenderermap();

 3     public void setrenderer(class renderedclass, objectrenderer renderer);

 5 hierarchy類實作了renderedsupprt接口,而且它的實作也很簡單:

 6 renderermap renderermap;

 7 public hierarchy(logger root) {

 8     

深入Log4J源碼之LoggerRepository和Configurator

 9     renderermap = new renderermap();

10     

深入Log4J源碼之LoggerRepository和Configurator

11 }

12 public void addrenderer(class classtorender, objectrenderer or) {

13     renderermap.put(classtorender, or);

14 }

15 public renderermap getrenderermap() {

16     return renderermap;

17 }

18 public void setrenderer(class renderedclass, objectrenderer renderer) {

19     renderermap.put(renderedclass, renderer);

在renderermap類實作中,它使用hastable儲存被渲染的類執行個體和相應的objectrender執行個體,在查找一個類是否存在注冊的渲染類時,如果它本身沒有找到,需要向上嘗試其父類和接口是否有注冊相應的objectrender類,如果都沒有找到,則傳回預設的objectrender。

 1 public objectrenderer get(class clazz) {

 2     objectrenderer r = null;

 3     for (class c = clazz; c != null; c = c.getsuperclass()) {

 4         r = (objectrenderer) map.get(c);

 5         if (r != null) {

 6             return r;

 7         }

 8         r = searchinterfaces(c);

 9         if (r != null)

10             return r;

12     return defaultrenderer;

13 }

14 objectrenderer searchinterfaces(class c) {

15     objectrenderer r = (objectrenderer) map.get(c);

16     if (r != null) {

17         return r;

18     } else {

19         class[] ia = c.getinterfaces();

20         for (int i = 0; i < ia.length; i++) {

21             r = searchinterfaces(ia[i]);

22             if (r != null)

23                 return r;

26     return null;

27 }

28 public objectrenderer getdefaultrenderer() {

29     return defaultrenderer;

30 }

31 public void put(class clazz, objectrenderer or) {

32     map.put(clazz, or);

33 }

throwablerenderersupport接口用于支援設定和擷取throwablerenderer,進而使用者可以自定義對throwable對象的渲染。

1 public interface throwablerenderersupport {

2     throwablerenderer getthrowablerenderer();

3     void setthrowablerenderer(throwablerenderer renderer);

4 }

hierarchy類以屬性的方式實作了該接口,因而每個hierarchy執行個體隻能有一個全局的throwablerenderer,而不能像objectrender那樣為不同的類定義不同的render。當時這種設計也是合理的,因為對throwable的渲染最主要的就是其棧的渲染,其他的沒什麼大的不同,而且對棧渲染方式保持相同的格式會比較好。

 1 private throwablerenderer throwablerenderer = null;

 2 public hierarchy(logger root) {

 3     

深入Log4J源碼之LoggerRepository和Configurator

 4     defaultfactory = new defaultcategoryfactory();

 5     

深入Log4J源碼之LoggerRepository和Configurator

 7 public void setthrowablerenderer(final throwablerenderer renderer) {

 8     throwablerenderer = renderer;

 9 }

10 public throwablerenderer getthrowablerenderer() {

11     return throwablerenderer;

configurator接口用于定義對配置檔案的解析。在log4j中配置檔案解析出來的所有資訊都可以放在loggerrepository中,因而configurator接口的定義非常簡單。

1 public interface configurator {

2     public static final string inherited = "inherited";

3     public static final string null = "null";

4     void doconfigure(url url, loggerrepository repository);

5 }

log4j支援兩種檔案形式的配置檔案:properties檔案和xml檔案,他們風别對應propertyconfigurator類和domconfigurator類。

propertyconfigurator類解析properties檔案的中的配置資訊,可以設定log4j.debug為true以打開log4j内部的日志資訊;另外propertyconfigurator還支援linux風格的變量,即所有${variable}形式的變量都會被系統中對應的屬性或配置檔案内部定義的屬性替換(先查找系統中的屬性,後查找配置檔案内部定義的屬性);但是propertyconfigurator不支援一些log4j中的進階功能,如自定義errorhandler和定義asyncappender等。

configurator中最重要的方法是doconfigure()方法,在propertyconfigurator實作中,首先将配置檔案對應的url讀取成properties對象:

 1 public void doconfigure(java.net.url configurl, loggerrepository hierarchy) {

 2     properties props = new properties();

深入Log4J源碼之LoggerRepository和Configurator

 4         uconn = configurl.openconnection();

 5         uconn.setusecaches(false);

 6         istream = uconn.getinputstream();

 7         props.load(istream);

深入Log4J源碼之LoggerRepository和Configurator

 9     doconfigure(props, hierarchy);

10 }

而後檢查是否設定了log4j.debug、log4j.reset、log4j.threshold等屬性,如果有則做相應的設定。這裡通過optionconverter.findandsubst()方法實作屬性的查找和變量資訊的替換。

 1 public void doconfigure(properties properties, loggerrepository hierarchy) {

 2     repository = hierarchy;

 3     string value = properties.getproperty(loglog.debug_key);

 4     if (value == null) {

 5         value = properties.getproperty("log4j.configdebug");

 6         if (value != null)

 7             loglog.warn("[log4j.configdebug] is deprecated. use [log4j.debug] instead.");

 9     if (value != null) {

10         loglog.setinternaldebugging(optionconverter.toboolean(value, true));

12     string reset = properties.getproperty(reset_key);

13     if (reset != null && optionconverter.toboolean(reset, false)) {

14         hierarchy.resetconfiguration();

15     }

16     string thresholdstr = optionconverter.findandsubst(threshold_prefix,

17             properties);

18     if (thresholdstr != null) {

19         hierarchy.setthreshold(optionconverter.tolevel(thresholdstr,

20                 (level) level.all));

21         loglog.debug("hierarchy threshold set to ["

22                 + hierarchy.getthreshold() + "].");

23     }

24     

深入Log4J源碼之LoggerRepository和Configurator

25 }

然後分三步解析配置資訊:

1.       解析root logger配置

首先找到log4j.rootlogger的值,它以逗号’,’分隔,其中第一個值時root的level資訊,之後是要添加到root的appender名字。對level資訊,直接設定給root就行。對appender名字,繼續解析。

 1 void parsecategory(properties props, logger logger, string optionkey,

 2         string loggername, string value) {

 3     loglog.debug("parsing for [" + loggername + "] with value=[" + value

 4             + "].");

 5     stringtokenizer st = new stringtokenizer(value, ",");

 6     if (!(value.startswith(",") || value.equals(""))) {

 7         if (!st.hasmoretokens())

 8             return;

 9         string levelstr = st.nexttoken();

10         loglog.debug("level token is [" + levelstr + "].");

11         if (inherited.equalsignorecase(levelstr)

12                 || null.equalsignorecase(levelstr)) {

13             if (loggername.equals(internal_root_name)) {

14                 loglog.warn("the root logger cannot be set to null.");

15             } else {

16                 logger.setlevel(null);

17             }

18         } else {

19             logger.setlevel(optionconverter.tolevel(levelstr,

20                     (level) level.debug));

21         }

22         loglog.debug("category " + loggername + " set to "

23                 + logger.getlevel());

24     }

25     logger.removeallappenders();

26     appender appender;

27     string appendername;

28     while (st.hasmoretokens()) {

29         appendername = st.nexttoken().trim();

30         if (appendername == null || appendername.equals(","))

31             continue;

32         loglog.debug("parsing appender named \"" + appendername + "\".");

33         appender = parseappender(props, appendername);

34         if (appender != null) {

35             logger.addappender(appender);

36         }

37     }

38 }

相同的appender可以添加到不同的logger中,因而propertyconfigurator對appender做了緩存,如果能從緩存中找到相應的appender類,則直接傳回找到的appender。

而後解析以下鍵值名以及對應類的屬性資訊:

log4j.appender.appendername=…

log4j.appender.appendername.layout=…

log4j.appender.appendername.errorhandler=…

log4j.appender.appendername.filter.filterkey.name=…

 1 appender parseappender(properties props, string appendername) {

 2     appender appender = registryget(appendername);

 3     if ((appender != null)) {

 4         loglog.debug("appender \"" + appendername

 5                 + "\" was already parsed.");

 6         return appender;

 7     }

 8     string prefix = appender_prefix + appendername;

 9     string layoutprefix = prefix + ".layout";

10     appender = (appender) optionconverter.instantiatebykey(props, prefix,

11             org.apache.log4j.appender.class, null);

12     if (appender == null) {

13         loglog.error("could not instantiate appender named \""

14                 + appendername + "\".");

15         return null;

16     }

17     appender.setname(appendername);

18     if (appender instanceof optionhandler) {

19         if (appender.requireslayout()) {

20             layout layout = (layout) optionconverter.instantiatebykey(

21                     props, layoutprefix, layout.class, null);

22             if (layout != null) {

23                 appender.setlayout(layout);

24                 loglog.debug("parsing layout options for \"" + appendername

25                         + "\".");

26                 propertysetter.setproperties(layout, props, layoutprefix

27                         + ".");

28                 loglog.debug("end of parsing for \"" + appendername + "\".");

29             }

31         final string errorhandlerprefix = prefix + ".errorhandler";

32         string errorhandlerclass = optionconverter.findandsubst(

33                 errorhandlerprefix, props);

34         if (errorhandlerclass != null) {

35             errorhandler eh = (errorhandler) optionconverter

36                     .instantiatebykey(props, errorhandlerprefix,

37                             errorhandler.class, null);

38             if (eh != null) {

39                 appender.seterrorhandler(eh);

40                 loglog.debug("parsing errorhandler options for \""

41                         + appendername + "\".");

42                 parseerrorhandler(eh, errorhandlerprefix, props, repository);

43                 final properties edited = new properties();

44                 final string[] keys = new string[] {

45                         errorhandlerprefix + "." + root_ref,

46                         errorhandlerprefix + "." + logger_ref,

47                         errorhandlerprefix + "." + appender_ref_tag };

48                 for (iterator iter = props.entryset().iterator(); iter

49                         .hasnext();) {

50                     map.entry entry = (map.entry) iter.next();

51                     int i = 0;

52                     for (; i < keys.length; i++) {

53                         if (keys[i].equals(entry.getkey()))

54                             break;

55                     }

56                     if (i == keys.length) {

57                         edited.put(entry.getkey(), entry.getvalue());

58                     }

59                 }

60                 propertysetter.setproperties(eh, edited, errorhandlerprefix

61                         + ".");

62                 loglog.debug("end of errorhandler parsing for \""

63                         + appendername + "\".");

64             }

65         }

66         propertysetter.setproperties(appender, props, prefix + ".");

67         loglog.debug("parsed \"" + appendername + "\" options.");

68     }

69     parseappenderfilters(props, appendername, appender);

70     registryput(appender);

71     return appender;

72 }

2.       解析loggerfactory配置

查找log4j.loggerfactory的值,儲存建立的loggerfactory執行個體,使用log4j.loggerfactory.propname的方式設定loggerfactory執行個體的屬性。

 1 protected void configureloggerfactory(properties props) {

 2     string factoryclassname = optionconverter.findandsubst(

 3             logger_factory_key, props);

 4     if (factoryclassname != null) {

 5         loglog.debug("setting category factory to [" + factoryclassname

 6                 + "].");

 7         loggerfactory = (loggerfactory) optionconverter

 8                 .instantiatebyclassname(factoryclassname,

 9                         loggerfactory.class, loggerfactory);

10         propertysetter.setproperties(loggerfactory, props, factory_prefix

11                 + ".");

12     }

3.       解析非root logger和objectrender配置

解析log4j.logger.、log4j.renderer.、log4j.throwablerenderer.等資訊。

另外,propertyconfigurator還通過propertywatchlog類支援每個一段時間檢查一次,如果發現配置檔案有改動,則自動重新加載配置資訊。

domconfigurator使用dom解析所有log4j配置檔案中的元素,并根據dom中的元素查找對應的rootlogger、logger、appender、layout等子產品。另外domconfigurator也支援每隔一段時間檢查檔案是否有修改,若有,則重新載入新修改後的配置檔案。這裡domconfigurator的實作方式和propertyconfigurator的實作方式類似,不再詳細介紹。

logmanager将configurator和loggerrepository整合在一起,它在初始化的時候找到log4j配置檔案,并且将其解析到loggerrepository中。

 1 static {

 2     hierarchy h = new hierarchy(new rootlogger((level) level.debug));

 3     repositoryselector = new defaultrepositoryselector(h);

 4     string override = optionconverter.getsystemproperty(

 5             default_init_override_key, null);

 6     if (override == null || "false".equalsignorecase(override)) {

 7         string configurationoptionstr = optionconverter.getsystemproperty(

 8                 default_configuration_key, null);

 9         string configuratorclassname = optionconverter.getsystemproperty(

10                 configurator_class_key, null);

11         url url = null;

12         if (configurationoptionstr == null) {

13             url = loader.getresource(default_xml_configuration_file);

14             if (url == null) {

15                 url = loader.getresource(default_configuration_file);

16             }

17         } else {

18             try {

19                 url = new url(configurationoptionstr);

20             } catch (malformedurlexception ex) {

21                 url = loader.getresource(configurationoptionstr);

22             }

23         }

24         if (url != null) {

25             loglog.debug("using url [" + url

26                     + "] for automatic log4j configuration.");

27             try {

28                 optionconverter.selectandconfigure(url,

29                         configuratorclassname,

30                         logmanager.getloggerrepository());

31             } catch (noclassdeffounderror e) {

32                 loglog.warn("error during default initialization", e);

33             }

34         } else {

35             loglog.debug("could not find resource: ["

36                     + configurationoptionstr + "].");

37         }

38     } else {

39         loglog.debug("default initialization of overridden by "

40                 + default_init_override_key + "property.");

41     }

42 }