vlambda博客
学习文章列表

日志框架Log4j源码解析(2)

 
   
   
 


上一篇Log4j源码解析我们分析了Logger、Appender。相信读者应该已经搞清楚了这两者的对应关系,一个是面向用户(Logger),一个是面向输出(控制台、文件、远程)。今天我们来看看根据配置文件进行初始化。首先我们先看一段Log4j的配置。

log4j.rootLogger=debug,stdout,info,debug,warn,error#log4j.threshold=fatal#consolelog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
log4j.appender.info=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'log4j.appender.info.File=./log/info.loglog4j.appender.info.Append=truelog4j.appender.info.Threshold=INFOlog4j.appender.info.layout=org.apache.log4j.PatternLayoutlog4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.appender.debug=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log'log4j.appender.debug.File=./log/debug.loglog4j.appender.debug.Append=truelog4j.appender.debug.Threshold=DEBUGlog4j.appender.debug.layout=org.apache.log4j.PatternLayoutlog4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.appender.warn=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log'log4j.appender.warn.File=./log/warn.loglog4j.appender.warn.Append=truelog4j.appender.warn.Threshold=WARNlog4j.appender.warn.layout=org.apache.log4j.PatternLayoutlog4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n#errorlog4j.appender.error = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.error.DatePattern='_'yyyy-MM-dd'.log'log4j.appender.error.File = ./log/error.loglog4j.appender.error.Append = truelog4j.appender.error.Threshold = ERRORlog4j.appender.error.layout = org.apache.log4j.PatternLayoutlog4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.logger.org.apache.log4j.LogManager=error,testlog4j.appender.test = com.erely.appender.MyAppenderlog4j.appender.test.Append=truelog4j.appender.test.layout = org.apache.log4j.PatternLayoutlog4j.appender.test.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%nlog4j.appender.test.filter.MyFilter=com.erely.filter.MyFilter


我们先简单分析一下配置文件,首先是log4j.rootLogger节点。这个节点也是一个Logger,是所有Logger的父亲,如果一个Logger没有配置Appender就会默认继承父类的Appender,根节点的值的第一个是Logger的日志级别,后面的就是定义的一些Appender,比如info

log4j.appender.info=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'log4j.appender.info.File=./log/info.loglog4j.appender.info.Append=truelog4j.appender.info.Threshold=INFOlog4j.appender.info.layout=org.apache.log4j.PatternLayoutlog4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n

这里边就定义了实现类,对应的文件、格式化、格式化正则表达等内容。在加载的时候应该是先找到rootLogger设置级别然后添加对应的Appender。可能我们要对某个类打日志单独处理。可以看到最后面加了一个LogManager的类的Logger,和rootLogger一样理解就可以,应该是比较简单的。接下来我们就通过源码来分析。在Log4j中获取Logger是通过LogManager来获取的。调用这个类的使用,该类的静态方法对配置文件进行了解析。在分析这些代码之前我们先看一看LoggerRepository接口的实现类Hierarchy

这个类还实现了RendererSupport、ThrowableRendererSupport,这里对这个两个我们不细说。我们看看上面列出来的成员变量。
  
    
    
  
private LoggerFactory defaultFactory; //Logger工厂   private Vector listeners; //监听集合 Hashtable ht; //存储Logger实例 Logger root; //根Logger   RendererMap rendererMap; int thresholdInt; //级别int   Level threshold;//日志级别 boolean emittedNoAppenderWarning = false; //Appender为空是否警告完毕 只会警告一次   boolean emittedNoResourceBundleWarning = false; //国际化资源为空是否警告 private ThrowableRenderer throwableRenderer = null; //
根据上面的变量相信读者已经能猜出来Hierarchy的功能。解析的时候会记录根Logger然后把其他的Logger放到ht中,另外设置日志一个日志的阈值(大于该阈值的级别才会打印)。这个里面有个设计就是emittedNoAppenderWarning这个是来警告在输出日志的时候没有找到Appender的但是这个至警告一次。笔者觉得作者认为如果多次警告就会占用系统资源,会降低性能。这个类中大部分方法都是set,get笔者对这些功能就不做介绍。我们来看看获取Logger的方法
  
    
    
  
public   Logger getLogger(String name, LoggerFactory factory) {     CategoryKey key = new CategoryKey(name); //通过name创建一个位置的Key     Logger logger; synchronized(ht) { //线程安全 Object o = ht.get(key); //从ht中获取Logger if(o == null) { //如果没有找到 logger = factory.makeNewLoggerInstance(name); //建造新的logger实例 logger.setHierarchy(this); //设置Logger中的Logger容器为当前类 ht.put(key, logger);//将该Logger放入ht中 updateParents(logger); //设置Logger的父类 return logger; //返回 } else if(o instanceof Logger) { //如果找到了直接返回 return (Logger) o;       } else if (o instanceof ProvisionNode) { //如果获取到的类是在创建父Logger的时候使用的占用符,同时父Loggername对应的是子Logger的实例在这里需要设置对应的关系 logger.setHierarchy(this); ht.put(key, logger); updateChildren((ProvisionNode) o, logger); //设置子Logger updateParents(logger); //更新父Logger return logger; }       else { return null; } } }
上面的代码应该很好理解,先根据name创建唯一的Key这个Key是由name和对应的hashCode组成的用来唯一表示某个Logger。然后就是通过名称获取Logger,如果没有获取到就创建,并设置一些属性。最后要设置对应的父Logger。我们来看看对应的方法
  
    
    
  
final private  void updateParents(Logger cat) { String name = cat.name; int length = name.length(); boolean parentFound = false; 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); // 构建父Logger的name       Object o = ht.get(key); //获取       if(o == null) { //如果为空 ProvisionNode pn = new ProvisionNode(cat); //创建一个占用 ht.put(key, pn); //将父key和子Logger放入ht中 } else if(o instanceof Category) { //如果存在父Logger则设置 parentFound = true;   cat.parent = (Category) o; break; // no need to update the ancestors of the closest ancestor } else if(o instanceof ProvisionNode) { //如果父Logger已经是ProvisionNode类型,则继续添加 ((ProvisionNode) o).addElement(cat); } else { Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht."); e.printStackTrace(); }     } if(!parentFound) //如果没有 则设置父Logger为根节点 cat.parent = root; }
上面是设置父Logger的过程,将报名一级一级往前拆出获取父类,如果父类存在并且是Logger则设置当前Logger。如果没有则先使用ProvisionNode进行占位。如果后面加载到了父Logger同时更新自己的信息和子类信息。如果都没有找到会设置父Logger为root。其他的方法这里就不介绍,遇到了就拿出来介绍下。我们继续看容器类LogManager。
  
    
    
  
  static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; //默认配置文   static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  //xml配置文件   static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration"; //配置key   static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass"; public static final String DEFAULT_INIT_OVERRIDE_KEY =                                                  "log4j.defaultInitOverride"; static private Object guard = null; static private RepositorySelector repositorySelector; //Logger仓库选择器
成员变量主要是配置文件名称。这里官网只留下了log4j.xml。其他的字段都标记过时了。但是笔者分析的话还是继续properties文件分析。下面我们看看静态代码块,这个方法里面就是在找配置文件,前面有很多代码都是去做检查这里笔者不介绍。
  
    
    
  
static { // By default we use a DefaultRepositorySelector which always returns 'h'. Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //初始化默认级别debug 初始化Logger容器     repositorySelector = new DefaultRepositorySelector(h);//创建Logger容器选择器   String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,                    null); //查找系统变量中log4j.defaultInitOverride的值     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) { //如果log4j.configuration为空 则去查找xml文件 url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); //载入log4j.xml if(url == null) { url = Loader.getResource(DEFAULT_CONFIGURATION_FILE); //如果没有则载入log4j.properties文件 } } else { //如果配置了log4j.configuration内容 try { url = new URL(configurationOptionStr); //查找内容   } catch (MalformedURLException ex) { url = Loader.getResource(configurationOptionStr); } }
if(url != null) { //如果文件URL不为空 LogLog.debug("Using URL ["+url+"] for automatic log4j configuration."); try {         //加载方法 入口 OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository()); //处理配置文件 TODO } 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."); } }
找到配置文件进行处理配置文件,根据不同的配置文件类型使用不同的加载器。这里就使用了策略模式,根据不同的处理情况使用不同的处理器进行处理。
  
    
    
  
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")) { //如果文件是xml结尾并且配置类为空设置默认的解析类 clazz = "org.apache.log4j.xml.DOMConfigurator"; } if(clazz != null) { //使用自定义加载器加载或者xml加载器 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); //加载配置文件 }
  
    
    
  
public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) { Properties props = new Properties(); LogLog.debug("Reading configuration from URL " + configURL); InputStream istream = null; URLConnection uConn = null; try { uConn = configURL.openConnection(); uConn.setUseCaches(false); istream = uConn.getInputStream(); props.load(istream); //载入所有属性 } catch (Exception e) { if (e instanceof InterruptedIOException || e instanceof InterruptedException) { Thread.currentThread().interrupt(); } LogLog.error("Could not read configuration file from URL [" + configURL + "].", e); LogLog.error("Ignoring configuration file [" + configURL +"]."); return; } finally { if (istream != null) { try { istream.close(); } catch(InterruptedIOException ignore) { Thread.currentThread().interrupt(); } catch(IOException ignore) { } catch(RuntimeException ignore) { } } } //加载入口 doConfigure(props, hierarchy); //处理配置 }
下面我们介绍真正的解析。可以看到上面用了很多方法去处理配置文件,首先是获取配置文件类型。然后是根据配置文件类型判断使用哪个加载器去加载。最后在使用加载器对配置实际的加载。这是遵守“单一职责”原则。我们看看具体加载
  
    
    
  
public void doConfigure(Properties properties, LoggerRepository hierarchy) { repository = hierarchy; String value = properties.getProperty(LogLog.DEBUG_KEY); //获取log.debug key的值 (log4j内部是否debug打印日志),如果为ture打印,false不打印 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)); //设置内部日志是否打印 }
// // if log4j.reset=true then // reset hierarchy String reset = properties.getProperty(RESET_KEY); //获取log4j.reset 如果设置为true,重置logger容器 if (reset != null && OptionConverter.toBoolean(reset, false)) { hierarchy.resetConfiguration(); //重置Logger容器 默认创建rootLogger }
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);//设置logger容器总阀值,低于阀值将不打印日志。如果没有配置,默认设置为最低级别ALL if(thresholdStr != null) { hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL)); //设置总阈值 LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"]."); } //上面都是在处理内部打印日志 相关 configureRootCategory(properties, hierarchy); //处理rootLogger appender configureLoggerFactory(properties); //处理Logger工厂 parseCatsAndRenderers(properties, hierarchy); //处理其他节点 非根节点
LogLog.debug("Finished configuring."); // We don't want to hold references to appenders preventing their // garbage collection. registry.clear(); }
这个类里面首先处理的是内部日志的打印,开发者可以设置对应的属性值,设置内部日志开关,内部日志阈值等等。处理完毕内部日志之后。在处理用户的日志配置内容。笔者觉得这一块很妙,将系统态和用户态隔离开,读者可以思考下是不是感觉很妙。我们再看看解析根节点的方法。
  
    
    
  
void configureRootCategory(Properties props, LoggerRepository hierarchy) { String effectiveFrefix = ROOT_LOGGER_PREFIX; //log4j.rootLogger前缀 String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props); //从配置文件从获取值
if(value == null) { value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props); //获取值log4j.rootCategory effectiveFrefix = ROOT_CATEGORY_PREFIX; //设置前缀 }
if(value == null) LogLog.debug("Could not find root logger information. Is this OK?"); else { Logger root = hierarchy.getRootLogger(); //获取跟Logger synchronized(root) { parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); //处理属性 } } }
上面通过根节点的属性获取对应的值然后进行处理,上面的代码从读者应该可以发现,原来根节点可以使用两种属性名。继续看代码
  
    
    
  
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)); //设置根Logger日志级别 } LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());     }     logger.removeAllAppenders(); //移除所有已存在的Appender 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); //解析appender if(appender != null) { logger.addAppender(appender); //Logger添加所有Appender } } }
先对属性值进行,分割第一个是日志级别,设置根Logger的日志级别,然后后面的就是关联的Appender,循环的去处理。我们来看一下如何处理Appender的
  
    
    
  
Appender parseAppender(Properties props, String appenderName) { Appender appender = registryGet(appenderName); //从注册器中获取appender if((appender != null)) { //如果不为空则说明已经有直接返回 LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); return appender; } // Appender was not previously initialized. String prefix = APPENDER_PREFIX + appenderName; //拼接前缀log4j.appender.+ 配置的名字 String layoutPrefix = prefix + ".layout"; //log4j.appender.+ 配置的名字+ .layout 获取打印规则
appender = (Appender) OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null); //创建appender if(appender == null) { //如果找不到直接返回空并打印错误日志 LogLog.error( "Could not instantiate appender named \"" + appenderName+"\"."); return null; } appender.setName(appenderName); //设置Appender名字
if(appender instanceof OptionHandler) { if(appender.requiresLayout()) { Layout layout = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, null); //获取Layout类 if(layout != null) { appender.setLayout(layout); //appender设置Layout LogLog.debug("Parsing layout options for \"" + appenderName +"\"."); //configureOptionHandler(layout, layoutPrefix + ".", props); 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); //Appender设置错误处理类 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 +"\"."); }
} //configureOptionHandler((OptionHandler) appender, prefix + ".", props); PropertySetter.setProperties(appender, props, prefix + "."); //设置相关属性 LogLog.debug("Parsed \"" + appenderName +"\" options."); } parseAppenderFilters(props, appenderName, appender);//设置过滤器 registryPut(appender); //这里将生成的appender放入Hashtable,以便于下次获取,不用重新生成 return appender; }
上面的代码中处理流程为,先使用log.append.+name拼接Appender的前缀,log.append.+name+.Layout.拼接布局的前缀,先获取Appender的实现类。创建实例,在通过获取到布局的类名创建实例,设置Appender的布局,同样获取错误处理器并设置。到这里一个大概的Appedner已经查不多了,但是我们发现还有一些属性没有设置到实例中呢。log4j会遍历所有的属性值,根据不同的前缀,获取到对应的属性名。然后获取属性的set方法。通过反射的方式将属性设置到实例中。处理完这些之后Appender就设置Filters了。然后在将appender放入一个Hashtable中(这个Hashtable是临时的,整个配置文件加载完毕之后会清除)。然后返回这个Appender。我们在回到解析根节点会将Appender加入到Logger中。
我们再看来解析工厂类的方法这个方法比较简单就是根据配置文件获取到对应的类名然后创建
  
    
    
  
protected void configureLoggerFactory(Properties props) { String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, props); //获取log4j.loggerFactory工厂 if(factoryClassName != null) { LogLog.debug("Setting category factory to ["+factoryClassName+"]."); loggerFactory = (LoggerFactory) OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory); PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + "."); //设置工厂类及属性 } }
到这里我们已经分析完了大部分包括rootLogger创建和处理Appender、处理Layout、Filter、错误处理等。最后一个处理是处理用户其他的Logger。
  
    
    
  
protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) { Enumeration enumeration = props.propertyNames(); //获取所有属性 while(enumeration.hasMoreElements()) { String key = (String) enumeration.nextElement(); //获取属性名 if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { //如果是以log4j.category.或log4j.logger.开始 String loggerName = null; if(key.startsWith(CATEGORY_PREFIX)) { loggerName = key.substring(CATEGORY_PREFIX.length()); //获取得到LoggerName } else if(key.startsWith(LOGGER_PREFIX)) { loggerName = key.substring(LOGGER_PREFIX.length()); } String value = OptionConverter.findAndSubst(key, props); //获取值 Logger logger = hierarchy.getLogger(loggerName, loggerFactory); //通过loggerName获取Logger synchronized(logger) { parseCategory(props, logger, key, loggerName, value); //处理Logger 和根节点一样处理 parseAdditivityForLogger(props, logger, loggerName); //是否继承父类的Appender } } else if(key.startsWith(RENDERER_PREFIX)) { //如果log4j.renderer.开始 这个是处理自定义的消息实体 String renderedClass = key.substring(RENDERER_PREFIX.length()); String renderingClass = OptionConverter.findAndSubst(key, props); if(hierarchy instanceof RendererSupport) { RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass, renderingClass); } } else if (key.equals(THROWABLE_RENDERER_PREFIX)) { if (hierarchy instanceof ThrowableRendererSupport) { ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(props, THROWABLE_RENDERER_PREFIX, org.apache.log4j.spi.ThrowableRenderer.class, null); if(tr == null) { LogLog.error( "Could not instantiate throwableRenderer."); } else { PropertySetter setter = new PropertySetter(tr); setter.setProperties(props, THROWABLE_RENDERER_PREFIX + "."); ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
} } } } }
这里处理的主要是三个一个是Logger,一个是自定义的消息处理,还有一个是异常。笔者这里只介绍Logger的处理。首先会根据前缀log4j.category.或log4j.logger.开始 获取到对应的Logger名称。然后调用和根节点一样的处理方法处理对应的Appender。处理完毕之后处理一个属性additivity。这个属性是代表子类是否使用父类的Appender。配置文件中的存在格式是log4j.additivity.+LoggerName。

上面提到消息实体,一般情况下我们打印日志的消息是String类型的,但是如果有一些特殊要求,当消息不再是String类型的而是某个自定义的类。那么我们要对这个消息进行处理就是上面log4j.renderer.来进行配置,最后还有一个是异常的这里笔者就不介绍了有兴趣的读者可以去看看源码。

到这里我们已经对整个加载过程做了分析,整体过程为

1.初始话Logger容器类,找到对应的配置文件。

2.根据配置文件的类型使用不同的处理器去处理配置文件。

3.先处理内部日志打印相关信息,然后在处理用户日志相关信息

4.处理rootLogger,主要包括设置日志级别、处理相关联的Appender

5.处理日志工厂(功能是创建Logger)。

6.处理另外的属性包括用户定义的Logger、自定义消息处理器Render、异常等。



长按上方二维码添加关注哦,会有更好的好文推荐给大家!
如果觉得文章不错,动动小手指点击右下角在看