码迷,mamicode.com
首页 > 其他好文 > 详细

log4j源码解析-文件解析

时间:2018-02-11 12:29:41      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:tokenizer   name   markdown   this   使用   result   系统   .com   star   

承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象创建。本文将在此基础上具体看下log4j是如何解析文件并输出我们所常见的日志格式

附例

文件的加载方式,我们就选举log4j.properties作为分析的文件例子,并附上相应的通用配置

log4j.rootLogger=info,stdout,logfile,errorfile

log4j.logger.org.apache=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.freemarker.core=error

#standout log appender #
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

#common log appender #
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File=../logs/appender-test/info.log
log4j.appender.logfile.append=true
log4j.appender.logfile.encoding=GB18030
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

#error log appender #
log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorfile.File=../logs/appender-test/error.log
log4j.appender.errorfile.Threshold=WARN
log4j.appender.errorfile.append=true
log4j.appender.errorfile.encoding=GB18030
log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout
log4j.appender.errorfile.layout.ConversionPattern=%d %p [%c] - %m%n

此处不详解,我们直接看源码方面是如何处理,从代码层面来通用理解下上述的配置

OptionConverter

操作log4j配置的主要工具类,所有的读取配置并封装成对象均以此类作为入口,其在LogManager的调用方式为
OptionConverter.selectAndConfigure(url,configuratorClassName,LogManager.getLoggerRepository());
入参作下简单的展示

url - 文件路径

clazz - log4j.configuratorClass属性对应的class,默认为null

hierarchy - log4j的层级管理类,存储log4j的通用配置,默认为Hierarchy类

OptionConverter#selectAndConfigure-解析配置入口

简单看下源码

static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
   Configurator configurator = null;
   String filename = url.getFile();
   //xml格式的文件则采用DOMConfigurator解析类,表明默认采用xml格式的解析方式
   if(clazz == null && filename != null && filename.endsWith(".xml")) {
     clazz = "org.apache.log4j.xml.DOMConfigurator";
   }

   if(clazz != null) {
     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 {
     //最后一种方式则为properties解析方式
     configurator = new PropertyConfigurator();
   }

   configurator.doConfigure(url, hierarchy);
  }

从简单的注释中我们可以得出log4j只支持两种方式的解析方式

1.DOMConfigurator-xml格式的解析器,默认

2.PropertyConfigurator-properties格式的解析器

本文则着重讲解.properties配置文件的解析,即关注PropertyConfigurator解析器

PropertyConfigurator#doConfigure-解析properties配置文件

读取文件的方式就不分析了,很常见的采用Properties类来存储数据,递上重要的逻辑片段代码

public void doConfigure(Properties properties, LoggerRepository hierarchy) {
    repository = hierarchy;
    // 读取log4j.debug配置,值为boolean型,表明内部log是否支持debug模式
    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));
    }

    //读取log4j.reset的boolean值,true代表使用默认的配置
    String reset = properties.getProperty(RESET_KEY);
    if (reset != null && OptionConverter.toBoolean(reset, false)) {
          hierarchy.resetConfiguration();
    }

    //log4j.threshold阈值配置,也就是告警级别配置
    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()+"].");
    }
    
    // 配置根分类,也就是rootLogger
    configureRootCategory(properties, hierarchy);
    // 配置Logger工厂
    configureLoggerFactory(properties);
    // 解析非root的其他配置
    parseCatsAndRenderers(properties, hierarchy);

    LogLog.debug("Finished configuring.");
    // 清空下缓存
    registry.clear();
  }

按照上面的解析顺序作下备注

1.解析log4j.debug/log4j.configDebug(boolean)

是否让log4j的内部输出源支持debug模式,其实也就是是否调用System.out.println()方法,默认不支持debug模式,支持warn/error模式。(支持System.err.println()方法)

2.解析log4j.reset(boolean)

是否重新设置log4j配置,默认不重新设置(Optional,作用微小)

3.解析log4j.threshold(String)

all/debug/info/warn/error/fatal配置告警级别,表明对所有的输出源,低于该等级则不输出

4.解析根节点rootLogger

5.解析日志工厂

6.解析非根节点

我们对后三步的操作作下简单的分析,加深我们对通用配置的理解

PropertyConfigurator#configureRootCategory-解析根节点

首先简单的看下里面的操作逻辑

void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    // log4j.rootLogger或者log4j.rootCategory,支持${}系统变量取值
    String effectiveFrefix = ROOT_LOGGER_PREFIX;
    String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);

    if(value == null) {
      value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
      effectiveFrefix = ROOT_CATEGORY_PREFIX;
    }

    if(value == null)
      LogLog.debug("Could not find root logger information. Is this OK?");
    else {
      Logger root = hierarchy.getRootLogger();
      synchronized(root) {
    // 关键代码
    parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
      }
    }
  }

承接上述的关键代码分析,此处的logger参数为rootLogger

/**
 **    This method must work for the root category as well.
   */
  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 the level value is inherited, set category level value to
      // null. We also check that the user has not specified inherited for the
      // root category.
      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);
      }
    }
  }

由以上的代码可以简单的得知log4j.rootLogger对应的配置项为{level},{appenderNames}

1.{level} - 日志等级,设置根日志的日志等级,应用于所有的输出源

2.{appenderNames} - 可配置多个输出源,以,为分隔符。并由此属性解析log4j.appender开头的配置项

再而分析了解下PropertyConfigurator#parseAppender()方法解析输出源,为了防止代码展示过多,我们截取主要的代码片段进行分析

Appender parseAppender(Properties props, String appenderName) {
    ....
    // log4j.appender.{appenderName}
    String prefix = APPENDER_PREFIX + appenderName;
    // log4j.appender.{appenderName}.layout
    String layoutPrefix = prefix + ".layout";
    // 首先根据log4j.appender.{appenderName}解析得到Appender对象
    appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
                          org.apache.log4j.Appender.class,
                          null);
    ...

    if(appender instanceof OptionHandler) {
      if(appender.requiresLayout()) {
      // 解析得到Layout对象,代表该输出源的输出格式
    Layout layout = (Layout) OptionConverter.instantiateByKey(props,
                                  layoutPrefix,
                                  Layout.class,
                                  null);
     ....
      // 解析log4j.appender.{appenderName}.errorhandler
      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 +"\".");
                  // 解析ErrorHandler对象
                  parseErrorHandler(eh, errorHandlerPrefix, props, repository);
                  ...
            }
          
      }
      ...
    }
    // 解析log4j.appender.{appenderName}.filter配置
    parseAppenderFilters(props, appenderName, appender);
    registryPut(appender);
    return appender;
  }

具体解析的过程就不讲解了,只在此处作下罗列,此处假定appenderName为console

1.log4j.appender.console - 对应的输出源的类名

2.log4j.appender.console.layout - 对应输出源的日志展示类名,通用为log4j.appender.{appenderName}.layout

3.log4j.appender.console.errorhandler-对应输出源的错误信息处理类名

4.log4j.appender.console.filter-输出源过滤类,支持配置多个。格式为log4j.appender.console.filter.{filterName}={filterClass}

PropertyConfigurator#configureLoggerFactory-解析日志工厂

解析的为log4j.loggerFactory配置,其可以指定logger工厂的实现类,默认为DefaultCategoryFactory,其内部就一个方法makeNewLoggerInstance()用于创建日志类Logger。用户可自定义实现

PropertyConfigurator#parseCatsAndRenderers-解析非根节点

解析的为log4j.logger/log4j.category/log4j.renderer/log4j.throwableRenderer配置,具体解析读者可自行分析

总结

见本文的分析,通过源码加深我们对配置的理解,心中多一份踏实

log4j源码解析-文件解析

标签:tokenizer   name   markdown   this   使用   result   系统   .com   star   

原文地址:https://www.cnblogs.com/question-sky/p/8436366.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!