配置&启动流程 of Log4j [1]

1. Log4j启动链路

  • 应用获取日志记录器,进而记录日志信息:
Logger logger = Logger.getLogger("demo.ab");
logger.info("I am a demo message.");
  • Logger获取日志记录器实现逻辑,具体调用LogManager.getLogger方法:
static public Logger getLogger(String name) {
  return LogManager.getLogger(name);
}
  • LogManager代理日志仓库logger repository相关的方法,获取Logger对象:
public static Logger getLogger(final String name) {
  return getLoggerRepository().getLogger(name);
}
  • LogManager的静态代码块,在类初始化时会启动并配置好整个log4j:

static {
  // 默认使用DefaultRepositorySelector
  Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
  repositorySelector = new DefaultRepositorySelector(h);

  // 获取系统属性log4j.defaultInitOverride
  String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null);
  if(override == null || "false".equalsIgnoreCase(override)) {
    // log4j.configuration 配置文件URL,如果配置则使用用户自定义配置文件路径
    String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
    // log4j.configuratorClass 配置文件解析器的类全限定名
    String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);
    URL url = null;

    // 如果没有设置系统属性log4j.configuration,则尝试CLASSPATH下的log4j.xml、log4j.properties配置文件
    if(configurationOptionStr == null) {  
      url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
      if(url == null) {
        url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
      }
    } else {
      try {
        // 如果设置了系统属性log4j.configuration,使用此属性指定的配置文件
        url = new URL(configurationOptionStr);
      } catch (MalformedURLException ex) {
        // 如果用户设置的资源不是一个URL,则尝试使用类加载器获取资源
        url = Loader.getResource(configurationOptionStr); 
      } 
    }
    
    // 如果到这里url不为null,表示需要配置log4j,将相关操作交付OptionConverter.selectAndConfigure处理
    if(url != null) {
      LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
      try {
        // 使用配置文件、以及配置解析器,具体配置整个log4j环境
        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 {
    // log4j.defaultInitOverride配置为true
    LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property."); 
  }  
} 
  • selectAndConfigure读取配置文件,使用Configurator将整个log4j体系配置起来:
static public
void selectAndConfigure(InputStream inputStream, String clazz, LoggerRepository hierarchy) {
  Configurator configurator = null;
  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 {
    configurator = new PropertyConfigurator();
  }
  configurator.doConfigure(inputStream, hierarchy);
}

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")) {
    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 {
     configurator = new PropertyConfigurator();
   }
  configurator.doConfigure(url, hierarchy);
}

2. Configurator

Configurator定义了log4j对配置文件解析处理相关的接口,主要api是2个doConfigure重载:

// 从一个io流中读取并配置log4j
void doConfigure(InputStream inputStream, LoggerRepository repository);
// 从一个url读取并配置log4j
void doConfigure(URL url, LoggerRepository repository);

Configurator的具体实现有:

  • PropertyConfigurator 从log4j.properties格式配置文件中读取并配置log4j
  • DOMConfigurator 从xml格式配置文件中读取并配置log4j
  • ReloadingPropertyConfigurator 重载并重新配置log4j

3. PropertyConfigurator

PropertyConfigurator会在类加载路径classpath下查找配置文件log4j.properties,如果配置文件找到,则具体调用PropertyConfigurator#configure(java.net.URL)进行配置读取和解析设置。相比于org.apache.log4j.xml.DOMConfigurator,PropertyConfigurator并没有支持一些高级特性的配置,比如错误处理器ErrorHandler、异步AsyncAppender等。

PropertyConfigurator支持变量替换功能,具体类似Unix Shell的语法,会有一些占位符,比如${java.home},并且系统属性java.home被设置为/home/xyz,则变量替换后${java.home}->/home/xyz。变量替换功能其实是提供模板占位符,log4j可以从系统属性中取值,或者系统属性不存在时,再到用户配置文件log4j.properties中寻找指定key的配置项值。

如果想要看log4j如何查找配置文件、解析配置文件的过程,可以设置系统属性log4j.debug为true,如下(也可以java命令行设置):

System.setProperty("log4j.debug", "true");

或者配置文件中配置:

log4j.debug=true
log4j.configDebug=true

log4j内部debug日志如下:

log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@4aa298b7.
log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@4aa298b7 class loader.
log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().
log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@4aa298b7.
log4j: Using URL [file:/Users/trafalgarluo/Documents/cdp/learn/target/classes/log4j.properties] for automatic log4j configuration.
log4j: Reading configuration from URL file:/Users/trafalgarluo/Documents/cdp/learn/target/classes/log4j.properties
log4j: Parsing for [root] with value=[INFO,writer].
log4j: Level token is [INFO].
log4j: Category root set to INFO
log4j: Parsing appender named "writer".
log4j: Parsing layout options for "writer".
log4j: Setting property [conversionPattern] to [%t %m%n].
log4j: End of parsing for "writer".
log4j: Setting property [name] to [writerdemo].
log4j: Setting property [target] to [System.out].
log4j: Setting property [threshold] to [DEBUG].
log4j: Setting property [encoding] to [utf8].
log4j: Setting property [immediateFlush] to [true].
log4j: Parsed "writer" options.
log4j: Finished configuring.

3.1 PropertyConfigurator配置文件格式

  • 配置项的格式是key=value
  • 重复调用PropertyConfigurator的配置方法,log4j已经完成配置过的相关组件并不会清理,如果需要清理可以调用LogManager#resetConfiguration函数。具体是否重置Hierarchy设置配置项:log4j.reset=true
  • Repository-wide threshold:仓库级别的日志过滤Level,配置格式log4j.threshold=[level],其中level可以为OFF, FATAL,ERROR, WARN, INFO, DEBUG, ALL或者其他自定义值
  • 配置根日志记录器语法为日志级别后跟多个Appender名,逗号分隔:log4j.rootLogger=[level], appenderName, appenderName, ...其中日志级别为可选配置项,可不配置(root日志记录器对应的级别未配置),也可以填写OFF, FATAL,ERROR, WARN, INFO, DEBUG, ALL。对于非根日志记录器,其语法类似如下:log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...logger_name表示日志记录器名,后续可跟可选的(日志级别level、继承父记录器的级别,NULL效果和INHERITED一样继承父亲),后续为可多个的Appender命,逗号分隔
  • Appender系列配置规则

a) 假设Appender名称为appenderName,名字中可以包含点号,后续跟此Appender的一些可配置属性,如:

log4j.appender.appenderName=Appender对应类的全限定名
log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN

b) 对于每一个命名Appender,可以配置其Layout属性,Layout对应也可配置其对应的属性,配置规则为:

log4j.appender.appenderName.layout=Layout对应类的全限定名
log4j.appender.appenderName.layout.option1=value1
....
log4j.appender.appenderName.layout.optionN=valueN

c) 对于每一命名Appender,可以配置其Filter过滤器,日志处理时一个Appender关联的多个Filter按照其在配置文件中的顺序调用其decide(LoggingEvent event)方法。具体每一个Filter可以配置其相应的属性,配置规则为(注意这里配置到了过滤器的标示id,每个Appender可以配置多个过滤器):

log4j.appender.appenderName.filter.ID=Filter过滤器对应类的全限定名
log4j.appender.appenderName.filter.ID.option1=value1
...
log4j.appender.appenderName.filter.ID.optionN=valueN

d) 每个Appender可以配置其ErrorHandler,配置规则为:

log4j.appender.appenderName.errorhandler=ErrorHandler对应类全限定名
log4j.appender.appenderName.errorhandler.root-ref={true|false}
log4j.appender.appenderName.errorhandler.logger-ref=loggerName
log4j.appender.appenderName.errorhandler.appender-ref=appenderName
log4j.appender.appenderName.errorhandler.option1=value1
...
log4j.appender.appenderName.errorhandler.optionN=valueN
  • org.apache.log4j.or.ObjectRenderer用于控制日志输出对象具体的序列化方式,规则为:
#log4j.renderer.后跟待渲染的对象全限定名,配置值为ObjectRenderer渲染实现类的全限定名
log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class,

#demo如:
log4j.renderer.my.Fruit=my.FruitRenderer
  • org.apache.log4j.spi.ThrowableRenderer用于控制Throwable渲染到字符串的方式:
log4j.throwableRenderer=ThrowableRenderer实现类的全限定名,log4j.throwableRenderer.paramName=paramValue

demo如:
log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer

一个完整的demo如下,#表示注释:

# 定义Appender A1
log4j.appender.A1=org.apache.log4j.net.SyslogAppender
log4j.appender.A1.SyslogHost=www.abc.net
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%r %-5p %c{2} %M.%L %x - %m\n

# 定义Appender A2
log4j.appender.A2=org.apache.log4j.RollingFileAppender
log4j.appender.A2.MaxFileSize=10MB
log4j.appender.A2.MaxBackupIndex=1
log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
log4j.appender.A2.layout.ContextPrinting=enabled
log4j.appender.A2.layout.DateFormat=ISO8601

# 根记录器对应级别DEBUG,对应Appender为A2
log4j.rootLogger=DEBUG, A2

# 继承自root根记录器,但是输出到A1,
log4j.logger.SECURITY=INHERIT, A1
log4j.additivity.SECURITY=false
log4j.logger.SECURITY.access=WARN

log4j.logger.class.of.the.day=INHERIT

3.2 PropertyConfigurator实现

PropertyConfigurator

PropertyConfigurator有字符串常量,定义了配置文件key名字:

static final String      CATEGORY_PREFIX = "log4j.category.";
static final String      LOGGER_PREFIX   = "log4j.logger.";
static final String      FACTORY_PREFIX = "log4j.factory";
static final String      ADDITIVITY_PREFIX = "log4j.additivity.";
static final String      ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
static final String      ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
static final String      APPENDER_PREFIX = "log4j.appender.";
static final String      RENDERER_PREFIX = "log4j.renderer.";
static final String      THRESHOLD_PREFIX = "log4j.threshold";
private static final String      THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
private static final String      LOGGER_REF = "logger-ref";
private static final String      ROOT_REF = "root-ref";
private static final String      APPENDER_REF_TAG = "appender-ref";
public static final String       LOGGER_FACTORY_KEY = "log4j.loggerFactory";
private static final String      RESET_KEY = "log4j.reset";
static final private String      INTERNAL_ROOT_NAME = "root";

PropertyConfigurator有4个doConfigure重载,
核心逻辑在第二个有Properties properties形参的重载中,其他几个都是根据相应的参数构造出Properties properties进而调用第二个重载:

public void doConfigure(String configFileName, LoggerRepository hierarchy)
public void doConfigure(Properties properties, LoggerRepository hierarchy)
public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) 
public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy)

第二个重载的具体逻辑:

  public void 
  doConfigure(Properties properties, LoggerRepository hierarchy) {
    repository = hierarchy;
    // 确认是否输出log4j内部细节信息,log4j.debug、log4j.configDebug顺序检查
    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=true,在执行配置前先重置Hierarchy仓库
    String reset = properties.getProperty(RESET_KEY);
    if (reset != null && OptionConverter.toBoolean(reset, false)) {
      hierarchy.resetConfiguration();
    }
    // log4j.threshold 设置仓库级别的Level
    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()+"].");
    }
    // 解析配置根日志记录器
    configureRootCategory(properties, hierarchy);
    // 解析配置日志工厂
    configureLoggerFactory(properties);
    // 解析配置Appender
    parseCatsAndRenderers(properties, hierarchy);
    LogLog.debug("Finished configuring.");
    // 避免持有Appender众的引用影响GC
    registry.clear();
  }

依次介绍下面几个实现:

  • configureRootCategory(properties, hierarchy); 解析配置根日志记录器
  • configureLoggerFactory(properties); 解析配置日志工厂,配置项为log4j.loggerFactory
  • parseCatsAndRenderers(properties, hierarchy); 解析配置日志记录器和Appender

3.2.1 解析配置根日志记录器

  void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    // 查看log4j.rootLogger配置项
    String effectiveFrefix = ROOT_LOGGER_PREFIX;
    String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
    // 如果没有log4j.rootLogger,兼容情况再看log4j.rootCategory配置项
    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);
      }
    }
  }

3.2.2 解析配置日志工厂

  protected void configureLoggerFactory(Properties props) {
    String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, props);
    if(factoryClassName != null) {
      LogLog.debug("Setting category factory to ["+factoryClassName+"].");
      //根据log4j.loggerFactory实例化一个日志工厂
      loggerFactory = (LoggerFactory)OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory);
      PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
    }
  }

3.2.3 解析配置日志记录器和Appender

  protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
    Enumeration enumeration = props.propertyNames();
    while(enumeration.hasMoreElements()) {
      String key = (String) enumeration.nextElement();
      // log4j.category. log4j.logger. 表示日志记录器配置,配置值中有Appender如
log4j.rootLogger=DEBUG, A2
      if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
        String loggerName = null;
        // 获取日志记录器的名字
        if(key.startsWith(CATEGORY_PREFIX)) {
          loggerName = key.substring(CATEGORY_PREFIX.length());
        } else if(key.startsWith(LOGGER_PREFIX)) {
          loggerName = key.substring(LOGGER_PREFIX.length());
        }
        // 获取key对应的配置值,支持变量替换,支持嵌套变量替换,如key1=luohw   key2=${key1}  可以获取key2对应value值为luohw
        String value =  OptionConverter.findAndSubst(key, props);
        // 获取日志记录器
        Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
        synchronized(logger) {
          // 关联日志记录器相关的属性配置
          parseCategory(props, logger, key, loggerName, value);
          // log4j.additivity. 配置日志记录器的additivity属性,表示子日志记录器是否继承父日志记录器的Appender
          parseAdditivityForLogger(props, logger, loggerName);
        }
      } else if(key.startsWith(RENDERER_PREFIX)) {
        // log4j.renderer.打头表示渲染器配置ObjectRenderers
        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)) {
        // log4j.throwableRenderer表示Throwable渲染器配置
        if (hierarchy instanceof ThrowableRendererSupport) {
          // 如果日志仓库hierarchy 支持渲染,即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);
          }
        }
      }
    }
  }

log4j.additivity配置日志记录器的additivity属性,表示子日志记录器是否继承父日志记录器的Appender

  void parseAdditivityForLogger(Properties props, Logger cat,
    String loggerName) {
    String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, props);
    LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
    // touch additivity only if necessary
    if((value != null) && (!value.equals(""))) {
      boolean additivity = OptionConverter.toBoolean(value, true);
      LogLog.debug("Setting additivity for \""+loggerName+"\" to "+ additivity);
      cat.setAdditivity(additivity);
    }
  }

日志记录器的一个创建和配置:

  /**
     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+"].");
    // We must skip over ',' but not white space
    StringTokenizer st = new StringTokenizer(value, ",");

    // If value is not in the form ", appender.." or "", then we should set
    // the level of the loggeregory.
    // 配置了日志级别
    if(!(value.startsWith(",") || value.equals(""))) {
      // just to be on the safe side...
      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());
    }

    // 异常已经绑定过的所有Appender
    logger.removeAllAppenders();
    Appender appender;
    String appenderName;
    while(st.hasMoreTokens()) {
      appenderName = st.nextToken().trim();
      if(appenderName == null || appenderName.equals(","))
        continue;
      // 解析具体每一个Appender并配置
      appender = parseAppender(props, appenderName);
      if(appender != null) {
        logger.addAppender(appender);
      }
    }
  }


  // 解析具体每一个Appender并配置
  Appender parseAppender(Properties props, String appenderName) {
    // 如果Appender已经存在,返回仓库中已有的
    Appender appender = registryGet(appenderName);
    if((appender != null)) {
      return appender;
    }
    // log4j.appender.前缀
    String prefix = APPENDER_PREFIX + appenderName;
    // Appender对应关联的Layout前缀log4j.appender.xxx.layout
    String layoutPrefix = prefix + ".layout";
    // 根据Appender的类全限定名实例化对象
    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 = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix,
          Layout.class, null);
        if(layout != null) {
          appender.setLayout(layout);
          // 根据反射,安装配置文件中key和Layout实例属性名字映射关系,初始化Layout的具体属性
          PropertySetter.setProperties(layout, props, layoutPrefix + ".");
        }
      }
      // 配置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);
          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 &lt 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 +"\".");
        }
      }
      // 根据反射,安装配置文件中key和Appender实例属性名字映射关系,初始化Appender的其他具体属性
      PropertySetter.setProperties(appender, props, prefix + ".");
    }
    // 配置日志Filter
    parseAppenderFilters(props, appenderName, appender);
    registryPut(appender);
    return appender;
  }
  
  // 配置ErrorHandler,ref后缀表示引用
  private void parseErrorHandler(final ErrorHandler eh, final String errorHandlerPrefix,
    final Properties props, final LoggerRepository hierarchy) {
    boolean rootRef = OptionConverter.toBoolean(
      OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
    if (rootRef) {
      eh.setLogger(hierarchy.getRootLogger());
    }
    String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
    if (loggerName != null) {
      Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
        : hierarchy.getLogger(loggerName, loggerFactory);
      eh.setLogger(logger);
    }
    String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
    if (appenderName != null) {
      Appender backup = parseAppender(props, appenderName);
      if (backup != null) {
        eh.setBackupAppender(backup);
      }
    }
  }

  // 配置日志过滤器,找到Filter->实例化Filter->Filter并和Appender管理起来
  void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
    // extract filters and filter options from props into a hashtable mapping
    // the property name defining the filter class to a list of pre-parsed
    // name-value pairs associated to that filter
    final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
    int fIdx = filterPrefix.length();
    Hashtable filters = new Hashtable();
    Enumeration e = props.keys();
    String name = "";
    while (e.hasMoreElements()) {
      String key = (String) e.nextElement();
      if (key.startsWith(filterPrefix)) {
        int dotIdx = key.indexOf('.', fIdx);
        String filterKey = key;
        if (dotIdx != -1) {
          filterKey = key.substring(0, dotIdx);
          name = key.substring(dotIdx+1);
        }
        Vector filterOpts = (Vector) filters.get(filterKey);
        if (filterOpts == null) {
          filterOpts = new Vector();
          filters.put(filterKey, filterOpts);
        }
        if (dotIdx != -1) {
          String value = OptionConverter.findAndSubst(key, props);
          filterOpts.add(new NameValue(name, value));
        }
      }
    }

    // sort filters by IDs, insantiate filters, set filter options, add filters to the appender
    Enumeration g = new SortedKeyEnumeration(filters);
    while (g.hasMoreElements()) {
      String key = (String) g.nextElement();
      String clazz = props.getProperty(key);
      if (clazz != null) {
        Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
        if (filter != null) {
          PropertySetter propSetter = new PropertySetter(filter);
          Vector v = (Vector)filters.get(key);
          Enumeration filterProps = v.elements();
          while (filterProps.hasMoreElements()) {
            NameValue kv = (NameValue)filterProps.nextElement();
            propSetter.setProperty(kv.key, kv.value);
          }
          propSetter.activate();
          appender.addFilter(filter);
        }
      } else {
        LogLog.warn("Missing class definition for filter: ["+key+"]");
      }
    }
  }

3.2.4 工具类OptionConverter

org.apache.log4j.config.PropertySetter是通用的属性设置器,propSetter.setProperty(kv.key, kv.value);具体反射调用关联对象属性的setXXX方法,具体是使用的工具是:java.beans包下的JavaBeans、Introspector。

PropertySetter ps = new PropertySetter(anObject);
ps.set("name", "Joe");
ps.set("age", "32");

ps.set("isMale", "true");
anObject.setName("Joe")
anObject.setAge(32),
anObject.setMale(true)

org.apache.log4j.config.PropertyGetter获取对象的属性:

PropertyGetter.getProperties(obj, this, fullname + ".");

org.apache.log4j.helpers.OptionConverter一些有用的工具函数,处理配置文件中的转义字符:

public static String convertSpecialChars(String s) {
  char c;
  int len = s.length();
  StringBuffer sbuf = new StringBuffer(len);

  int i = 0;
  while(i < len) {
    c = s.charAt(i++);
    if (c == '\\') {
      c =  s.charAt(i++);
      if(c == 'n')      c = '\n';
      else if(c == 'r') c = '\r';
      else if(c == 't') c = '\t';
      else if(c == 'f') c = '\f';
      else if(c == '\b') c = '\b';
      else if(c == '\"') c = '\"';
      else if(c == '\'') c = '\'';
      else if(c == '\\') c = '\\';
    }
    sbuf.append(c);
  }
  return sbuf.toString();
}

获取系统属性:

public static String getSystemProperty(String key, String def) {
  try {
    return System.getProperty(key, def);
  } catch(Throwable e) { 
    // MS-Java throws com.ms.security.SecurityExceptionEx
    LogLog.debug("Was not allowed to read system property \""+key+"\".");
    return def;
  }
}

根据类名反射生成实例:

public static Object instantiateByClassName(String className, Class superClass, Object defaultValue) {
  if(className != null) {
    try {
      Class classObj = Loader.loadClass(className);
      if(!superClass.isAssignableFrom(classObj)) {
        return defaultValue;
      }
      return classObj.newInstance();
    } catch (ClassNotFoundException e) {
    LogLog.error("Could not instantiate class [" + className + "].", e);
    } catch (IllegalAccessException e) {
    LogLog.error("Could not instantiate class [" + className + "].", e);
    } catch (InstantiationException e) {
      LogLog.error("Could not instantiate class [" + className + "].", e);
    } catch (RuntimeException e) {
    LogLog.error("Could not instantiate class [" + className + "].", e);
    }
  }
  return defaultValue;
}

根据key在Properties 中获取配置,支持嵌套的变量替换,比如p1=luohw p2=i like ${p1} too 则获p2对应的值时,获取为i like luohw too。这段代码对于做代码变量替换、实现模板引擎等需求也是通用的,可以参考:

  public static
  String findAndSubst(String key, Properties props) {
    String value = props.getProperty(key);
    if(value == null)
      return null;
    try {
      return substVars(value, props);
    } catch(IllegalArgumentException e) {
      LogLog.error("Bad option value ["+value+"].", e);
      return value;
    }
  }

递归调用过程:

  static String DELIM_START = "${";
  static char   DELIM_STOP  = '}';
  static int DELIM_START_LEN = 2;
  static int DELIM_STOP_LEN  = 1;

  public static
  String substVars(String val, Properties props) throws IllegalArgumentException {
    StringBuffer sbuf = new StringBuffer();
    int i = 0;
    int j, k;

    while(true) {
      j=val.indexOf(DELIM_START, i);
      if(j == -1) {
        // no more variables
        if(i==0) { // this is a simple string
          return val;
        } else { // add the tail string which contails no variables and return the result.
          sbuf.append(val.substring(i, val.length()));
          return sbuf.toString();
        }
      } else {
        sbuf.append(val.substring(i, j));
        k = val.indexOf(DELIM_STOP, j);
        if(k == -1) {
          throw new IllegalArgumentException('"'+val+
              "\" has no closing brace. Opening brace at position " + j
            + '.');
        } else {
          j += DELIM_START_LEN;
          String key = val.substring(j, k);
          // first try in System properties
          String replacement = getSystemProperty(key, null);
          // then try props parameter
          if(replacement == null && props != null) {
            replacement =  props.getProperty(key);
          }
          if(replacement != null) {
            // Do variable substitution on the replacement string
            // such that we can solve "Hello ${x2}" as "Hello p1" 
            // the where the properties are
            // x1=p1
            // x2=${x1}
            String recursiveReplacement = substVars(replacement, props);
            sbuf.append(recursiveReplacement);
          }
          i = k + DELIM_STOP_LEN;
        }
      }
    }
  }

to be continued ...

标签: none
添加新评论