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的接口定义如下:
public interface LoggerRepository { public void addHierarchyEventListener(HierarchyEventListener listener); boolean isDisabled(int level); public void setThreshold(Level level); public void setThreshold(String val); public void emitNoAppenderWarning(Category cat); public Level getThreshold(); public Logger getLogger(String name); public Logger getLogger(String name, LoggerFactory factory); public Logger getRootLogger(); public abstract Logger exists(String name); public abstract void shutdown(); public Enumeration getCurrentLoggers(); public abstract void fireAddAppenderEvent(Category logger, Appender appender); public abstract void resetConfiguration(); }
Hierarchy类
Hierarchy是Log4J中默认对LoggerRepository的实现类,它用于表达其内部的Logger是以层次结构存储的。在对LoggerRepository接口的实现中,getLogger()方法是其最核心的实现,因而首先从这个方法开始。
Hierarchy中用一个Hashtable来存储所有Logger实例,它以CategoryKey作为key,Logger作为value, 其中CategoryKey是对Logger中Name字符串的封装,之所以要引入这个类是出于性能考虑,因为它会缓存Name字符串的hash code,这样在查找过程中计算hash code时就可以直接取得而不用每次都计算。
class CategoryKey { String name; int hashCache; CategoryKey(String name) { this.name = name; hashCache = name.hashCode(); } final public int hashCode() { return hashCache; } final public boolean equals(Object rArg) { if (this == rArg) return true; if (rArg != null && CategoryKey.class == rArg.getClass()) return name.equals(((CategoryKey) rArg).name); else return false; } }
getLogger()方法中有一个重载函数提供LoggerFactory接口,它用于没有在LoggerRepository中找到Logger实例 时创建相应的Logger实例,默认实现直接创建一个Logger实例,用户可以通过自定义LoggerFactory实现创建自己的Logger实例。
public interface LoggerFactory { public Logger makeNewLoggerInstance(String name); } class DefaultCategoryFactory implements LoggerFactory { public Logger makeNewLoggerInstance(String name) { return new Logger(name); } }
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实例作为其子节点。
class ProvisionNode extends Vector { ProvisionNode(Logger logger) { super(); this.addElement(logger); } } final private void updateParents(Logger cat) { String name = cat.name; int length = name.length(); boolean parentFound = false; // if name = "x.y.z.w", loop thourgh "x.y.z", "x.y" and "x" for (int i = name.lastIndexOf('.', length - 1); i >= 0; i = name .lastIndexOf('.', i - 1)) { String substr = name.substring(0, i); CategoryKey key = new CategoryKey(substr); Object o = ht.get(key); if (o == null) { ProvisionNode pn = new ProvisionNode(cat); ht.put(key, pn); } else if (o instanceof Category) { parentFound = true; cat.parent = (Category) o; break; // no need to update the ancestors of the closest // ancestor } else if (o instanceof ProvisionNode) { ((ProvisionNode) o).addElement(cat); } else { Exception e = new IllegalStateException( "unexpected object type " + o.getClass() + " in ht."); e.printStackTrace(); } } // If we could not find any existing parents, then link with root. if (!parentFound) cat.parent = root; }
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实例的父节点。
final private void updateChildren(ProvisionNode pn, Logger logger) { final int last = pn.size(); for (int i = 0; i < last; i++) { Logger l = (Logger) pn.elementAt(i); // Unless this child already points to a correct (lower) parent, // make cat.parent point to l.parent and l.parent to cat. if (!l.parent.name.startsWith(logger.name)) { logger.parent = l.parent; l.parent = logger; } } }
综合起来,getLogger()方法的实现代码如下:
public Logger getLogger(String name, LoggerFactory factory) { CategoryKey key = new CategoryKey(name); Logger logger; synchronized (ht) { Object o = ht.get(key); if (o == null) { logger = factory.makeNewLoggerInstance(name); logger.setHierarchy(this); ht.put(key, logger); updateParents(logger); return logger; } else if (o instanceof Logger) { return (Logger) o; } else if (o instanceof ProvisionNode) { logger = factory.makeNewLoggerInstance(name); logger.setHierarchy(this); ht.put(key, logger); updateChildren((ProvisionNode) o, logger); updateParents(logger); return logger; } else { // It should be impossible to arrive here return null; // but let's keep the compiler happy. } } }
其他的方法实现则比较简单,对LoggerRepository来说,它也可以像其注册HierarchyEventListener监听器,每当向一个Logger添加或删除Appender,该监听器就会触发。
public interface HierarchyEventListener { public void addAppenderEvent(Category cat, Appender appender); public void removeAppenderEvent(Category cat, Appender appender); } private Vector listeners; public void addHierarchyEventListener(HierarchyEventListener listener) { if (listeners.contains(listener)) { LogLog.warn("Ignoring attempt to add an existent listener."); } else { listeners.addElement(listener); } } public void fireAddAppenderEvent(Category logger, Appender appender) { if (listeners != null) { int size = listeners.size(); HierarchyEventListener listener; for (int i = 0; i < size; i++) { listener = (HierarchyEventListener) listeners.elementAt(i); listener.addAppenderEvent(logger, appender); } } } void fireRemoveAppenderEvent(Category logger, Appender appender) { if (listeners != null) { int size = listeners.size(); HierarchyEventListener listener; for (int i = 0; i < size; i++) { listener = (HierarchyEventListener) listeners.elementAt(i); listener.removeAppenderEvent(logger, appender); } } }
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()方法。
public interface RendererSupport { public RendererMap getRendererMap(); public void setRenderer(Class renderedClass, ObjectRenderer renderer); }
Hierarchy类实现了RenderedSupprt接口,而且它的实现也很简单:
RendererMap rendererMap; public Hierarchy(Logger root) { rendererMap = new RendererMap(); } public void addRenderer(Class classToRender, ObjectRenderer or) { rendererMap.put(classToRender, or); } public RendererMap getRendererMap() { return rendererMap; } public void setRenderer(Class renderedClass, ObjectRenderer renderer) { rendererMap.put(renderedClass, renderer); }
在RendererMap类实现中,它使用Hastable保存被渲染的类实例和相应的ObjectRender实例,在查找一个类是否存在注册的渲染类时,如果它本身没有找到,需要向上尝试其父类和接口是否有注册相应的ObjectRender类,如果都没有找到,则返回默认的ObjectRender。
public ObjectRenderer get(Class clazz) { ObjectRenderer r = null; for (Class c = clazz; c != null; c = c.getSuperclass()) { r = (ObjectRenderer) map.get(c); if (r != null) { return r; } r = searchInterfaces(c); if (r != null) return r; } return defaultRenderer; } ObjectRenderer searchInterfaces(Class c) { ObjectRenderer r = (ObjectRenderer) map.get(c); if (r != null) { return r; } else { Class[] ia = c.getInterfaces(); for (int i = 0; i < ia.length; i++) { r = searchInterfaces(ia[i]); if (r != null) return r; } } return null; } public ObjectRenderer getDefaultRenderer() { return defaultRenderer; } public void put(Class clazz, ObjectRenderer or) { map.put(clazz, or); }
ThrowableRendererSupport接口
ThrowableRendererSupport接口用于支持设置和获取ThrowableRenderer,从而用户可以自定义对Throwable对象的渲染。
public interface ThrowableRendererSupport { ThrowableRenderer getThrowableRenderer(); void setThrowableRenderer(ThrowableRenderer renderer); }
Hierarchy类以属性的方式实现了该接口,因而每个Hierarchy实例只能有一个全局的ThrowableRenderer,而不能像 ObjectRender那样为不同的类定义不同的render。当时这种设计也是合理的,因为对Throwable的渲染最主要的就是其栈的渲染,其他的没什么大的不同,而且对栈渲染方式保持相同的格式会比较好。
private ThrowableRenderer throwableRenderer = null; public Hierarchy(Logger root) { defaultFactory = new DefaultCategoryFactory(); } public void setThrowableRenderer(final ThrowableRenderer renderer) { throwableRenderer = renderer; } public ThrowableRenderer getThrowableRenderer() { return throwableRenderer; }
Configurator接口
Configurator接口用于定义对配置文件的解析。在Log4J中配置文件解析出来的所有信息都可以放在LoggerRepository中,因而Configurator接口的定义非常简单。
public interface Configurator { public static final String INHERITED = "inherited"; public static final String NULL = "null"; void doConfigure(URL url, LoggerRepository repository); }
Log4J支持两种文件形式的配置文件:properties文件和xml文件,他们风别对应PropertyConfigurator类和DOMConfigurator类。
PropertyConfigurator类
PropertyConfigurator类解析properties文件的中的配置信息,可以设置log4j.debug为true以打开Log4J内 部的日志信息;另外PropertyConfigurator还支持Linux风格的变量,即所有${variable}形式的变量都会被系统中对应的属 性或配置文件内部定义的属性替换(先查找系统中的属性,后查找配置文件内部定义的属性);但是PropertyConfigurator不支持一些 Log4J中的高级功能,如自定义ErrorHandler和定义AsyncAppender等。
Configurator中最重要的方法是doConfigure()方法,在PropertyConfigurator实现中,首先将配置文件对应的URL读取成Properties对象:
public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) { Properties props = new Properties(); uConn = configURL.openConnection(); uConn.setUseCaches(false); istream = uConn.getInputStream(); props.load(istream); doConfigure(props, hierarchy); }
而后检查是否设置了log4j.debug、log4j.reset、log4j.threshold等属性,如果有则做相应的设置。这里通过OptionConverter.findAndSubst()方法实现属性的查找和变量信息的替换。
public void doConfigure(Properties properties, LoggerRepository hierarchy) { repository = hierarchy; String value = properties.getProperty(LogLog.DEBUG_KEY); if (value == null) { value = properties.getProperty("log4j.configDebug"); if (value != null) LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); } if (value != null) { LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); } String reset = properties.getProperty(RESET_KEY); if (reset != null && OptionConverter.toBoolean(reset, false)) { hierarchy.resetConfiguration(); } String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties); if (thresholdStr != null) { hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL)); LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "]."); } }
然后分三步解析配置信息:
1. 解析Root Logger配置
首先找到log4j.rootLogger的值,它以逗号’,’分隔,其中第一个值时root的Level信息,之后是要添加到root的Appender名字。对Level信息,直接设置给root就行。对Appender名字,继续解析。
void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) { LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "]."); StringTokenizer st = new StringTokenizer(value, ","); if (!(value.startsWith(",") || value.equals(""))) { if (!st.hasMoreTokens()) return; String levelStr = st.nextToken(); LogLog.debug("Level token is [" + levelStr + "]."); if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) { if (loggerName.equals(INTERNAL_ROOT_NAME)) { LogLog.warn("The root logger cannot be set to null."); } else { logger.setLevel(null); } } else { logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG)); } LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); } logger.removeAllAppenders(); Appender appender; String appenderName; while (st.hasMoreTokens()) { appenderName = st.nextToken().trim(); if (appenderName == null || appenderName.equals(",")) continue; LogLog.debug("Parsing appender named \"" + appenderName + "\"."); appender = parseAppender(props, appenderName); if (appender != null) { logger.addAppender(appender); } } }
相同的Appender可以添加到不同的Logger中,因而PropertyConfigurator对Appender做了缓存,如果能从缓存中找到相应的Appender类,则直接返回找到的Appender。
而后解析以下键值名以及对应类的属性信息:
log4j.appender.appenderName=…
log4j.appender.appenderName.layout=…
log4j.appender.appenderName.errorhandler=…
log4j.appender.appenderName.filter.filterKey.name=…
Appender parseAppender(Properties props, String appenderName) { Appender appender = registryGet(appenderName); if ((appender != null)) { LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); return appender; } String prefix = APPENDER_PREFIX + appenderName; String layoutPrefix = prefix + ".layout"; appender = (Appender) OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null); if (appender == null) { LogLog.error("Could not instantiate appender named \"" + appenderName + "\"."); return null; } appender.setName(appenderName); if (appender instanceof OptionHandler) { if (appender.requiresLayout()) { Layout layout = (Layout) OptionConverter.instantiateByKey( props, layoutPrefix, Layout.class, null); if (layout != null) { appender.setLayout(layout); LogLog.debug("Parsing layout options for \"" + appenderName + "\"."); PropertySetter.setProperties(layout, props, layoutPrefix + "."); LogLog.debug("End of parsing for \"" + appenderName + "\"."); } } final String errorHandlerPrefix = prefix + ".errorhandler"; String errorHandlerClass = OptionConverter.findAndSubst( errorHandlerPrefix, props); if (errorHandlerClass != null) { ErrorHandler eh = (ErrorHandler) OptionConverter .instantiateByKey(props, errorHandlerPrefix, ErrorHandler.class, null); if (eh != null) { appender.setErrorHandler(eh); LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\"."); parseErrorHandler(eh, errorHandlerPrefix, props, repository); final Properties edited = new Properties(); final String[] keys = new String[] { errorHandlerPrefix + "." + ROOT_REF, errorHandlerPrefix + "." + LOGGER_REF, errorHandlerPrefix + "." + APPENDER_REF_TAG }; for (Iterator iter = props.entrySet().iterator(); iter .hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); int i = 0; for (; i < keys.length; i++) { if (keys[i].equals(entry.getKey())) break; } if (i == keys.length) { edited.put(entry.getKey(), entry.getValue()); } } PropertySetter.setProperties(eh, edited, errorHandlerPrefix + "."); LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\"."); } } PropertySetter.setProperties(appender, props, prefix + "."); LogLog.debug("Parsed \"" + appenderName + "\" options."); } parseAppenderFilters(props, appenderName, appender); registryPut(appender); return appender; }
2. 解析LoggerFactory配置
查找log4j.loggerFactory的值,保存创建的LoggerFactory实例,使用log4j.loggerFactory.propName的方式设置LoggerFactory实例的属性。
protected void configureLoggerFactory(Properties props) { String factoryClassName = OptionConverter.findAndSubst( LOGGER_FACTORY_KEY, props); if (factoryClassName != null) { LogLog.debug("Setting category factory to [" + factoryClassName + "]."); loggerFactory = (LoggerFactory) OptionConverter .instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory); PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + "."); } }
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中。
static { Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); repositorySelector = new DefaultRepositorySelector(h); String override = OptionConverter.getSystemProperty( DEFAULT_INIT_OVERRIDE_KEY, null); 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 (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) { url = Loader.getResource(configurationOptionStr); } } if (url != null) { LogLog.debug("Using URL [" + url + "] for automatic log4j configuration."); try { OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository()); } catch (NoClassDefFoundError e) { LogLog.warn("Error during default initialization", e); } } else { LogLog.debug("Could not find resource: [" + configurationOptionStr + "]."); } } else { LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property."); } }