深入Log4J源碼之LoggerRepository和Configurator – JAVA編程語言程序開發技術文章

LoggerRepository從字面上理解,它是一個Logger的容器,它會創建並緩存Logger實例,從而具有相同名字的Logger實例不會多次創建,以提高性能。它的這種特性有點類似Spring的IOC概念。Log4J支持兩種配置文件:properties文件和xml文件。Configurator解析配置文件,並將解析後的信息添加到LoggerRepository中。LogManager最終將LoggerRepository和Configurator整合在一起。

LoggerRepository接口

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類

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         }
11     }
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);
11     }
12 }
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接口
RendererSupport接口支持用戶為不同的類設置相應的ObjectRender實例,從而可以從被渲染的類中或許更多的信息而不是默認的調用其toString()方法。

 1 public interface RendererSupport {
 2     public RendererMap getRendererMap();
 3     public void setRenderer(Class renderedClass, ObjectRenderer renderer);
 4 }
 5 Hierarchy類實現瞭RenderedSupprt接口,而且它的實現也很簡單:
 6 RendererMap rendererMap;
 7 public Hierarchy(Logger root) {
 8    
 9     rendererMap = new RendererMap();
10    
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);
20 }
在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;
11     }
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;
24         }
25     }
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接口
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    
 4     defaultFactory = new DefaultCategoryFactory();
 5    
 6 }
 7 public void setThrowableRenderer(final ThrowableRenderer renderer) {
 8     throwableRenderer = renderer;
 9 }
10 public ThrowableRenderer getThrowableRenderer() {
11     return throwableRenderer;
12 }
Configurator接口

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類
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();
 3    
 4         uConn = configURL.openConnection();
 5         uConn.setUseCaches(false);
 6         istream = uConn.getInputStream();
 7         props.load(istream);
 8    
 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.");
 8     }
 9     if (value != null) {
10         LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
11     }
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    
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             }
30         }
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     }
13 }
3.       解析非Root Logger和ObjectRender配置

解析log4j.logger.、log4j.renderer.、log4j.throwableRenderer.等信息。

另外,PropertyConfigurator還通過PropertyWatchLog類支持每個一段時間檢查一次,如果發現配置文件有改動,則自動重新加載配置信息。

DOMConfigurator類
DOMConfigurator使用DOM解析所有Log4J配置文件中的元素,並根據DOM中的元素查找對應的RootLogger、Logger、Appender、Layout等模塊。另外DOMConfigurator也支持每隔一段時間檢查文件是否有修改,若有,則重新載入新修改後的配置文件。這裡DOMConfigurator的實現方式和PropertyConfigurator的實現方式類似,不再詳細介紹。www.aiwalls.com

LogManager類

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 }
 

 作者:上善若水

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *