深入Log4J原始碼之LoggerRepository和Configurator
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的實現方式類似,不再詳細介紹。
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 }