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

4. DOMConfigurator

DOMConfigurator具体和PropertyConfigurator整体比较类似,支持log4j从一个xml文件中读取并初始化配置。DOMConfigurator使用了javax.xml包下的一些工具类实现配置读取和解析,功能理解上认识PropertyConfigurator就好,两者主要是配置文件格式引起的解析流程区别。

4.1 XML格式配置文件格式完整说明:

DOMConfigurator支持解析的xml配置文件格式,可以使用DOMConfigurator相同包下的log4j.dtd说明。

<!-- 一份完整的配置包括:renderer、root、appender、logger等,问号?表示0个或1个、星号表示0个或多个、|表示或条件. -->
<!ELEMENT log4j:configuration (renderer*, throwableRenderer?,
  appender*,plugin*, (category|logger)*,root?,
  (categoryFactory|loggerFactory)?)>
<!-- threshold设置日志级别,可以为all|trace|debug|info|warn|error|fatal|off|null,低于设置级别的日志不会输出 -->
<!-- debug设置是否打印log4j内部的一些信息,默认为null -->
<!-- 这里threshold的null表示没有更新具体日志级别值,保持我原有设置,debug也是类似的情况 -->
<!ATTLIST log4j:configuration
  xmlns:log4j              CDATA #FIXED "http://jakarta.apache.org/log4j/"
  threshold                (all|trace|debug|info|warn|error|fatal|off|null) "null"
  debug                    (true|false|null)  "null"
  reset                    (true|false) "false"
>

<!-- renderer用于配置消息对象字符串序列化的方式,有renderedClass、renderingClass属性  -->
<!ELEMENT renderer EMPTY>
<!ATTLIST renderer
  renderedClass  CDATA #REQUIRED
  renderingClass CDATA #REQUIRED
>

<!-- throwableRenderer控制异常相关信息的字符串序列化方式,有class属性 -->
<!ELEMENT throwableRenderer (param*)>
<!ATTLIST throwableRenderer
  class  CDATA #REQUIRED
>
<!-- Appenders 需要有name、class属性,分别表示Appender的名字和实现类. -->
<!-- Appenders 可以包含ErrorHandler、Layout、以及其他一些可选参数,如Filter、引用其他Appender等。 -->
<!-- 不在继续翻译了... -->

<!ELEMENT appender (errorHandler?, param*,
  rollingPolicy?, triggeringPolicy?, connectionSource?,
  layout?, filter*, appender-ref*)>
<!ATTLIST appender
  name CDATA #REQUIRED
  class CDATA #REQUIRED
>

<!ELEMENT layout (param*)>
<!ATTLIST layout
  class CDATA #REQUIRED
>

<!ELEMENT filter (param*)>
<!ATTLIST filter
  class CDATA #REQUIRED
>

<!-- ErrorHandlers can be of any class. They can admit any number of parameters-->
<!ELEMENT errorHandler (param*, root-ref?, logger-ref*,  appender-ref?)>
<!ATTLIST errorHandler
  class        CDATA   #REQUIRED
>

<!ELEMENT root-ref EMPTY>

<!ELEMENT logger-ref EMPTY>
<!ATTLIST logger-ref
  ref CDATA #REQUIRED
>

<!ELEMENT param EMPTY>
<!ATTLIST param
  name CDATA   #REQUIRED
  value CDATA #REQUIRED
>
<!-- The priority class is org.apache.log4j.Level by default -->
<!ELEMENT priority (param*)>
<!ATTLIST priority
  class   CDATA #IMPLIED
  value   CDATA #REQUIRED
>

<!-- The level class is org.apache.log4j.Level by default -->
<!ELEMENT level (param*)>
<!ATTLIST level
  class   CDATA #IMPLIED
  value   CDATA #REQUIRED
>
<!-- If no level element is specified, then the configurator MUST not -->
<!-- touch the level of the named category. -->
<!ELEMENT category (param*,(priority|level)?,appender-ref*)>
<!ATTLIST category
  class         CDATA   #IMPLIED
  name CDATA #REQUIRED
  additivity (true|false) "true"
>

<!-- If no level element is specified, then the configurator MUST not -->
<!-- touch the level of the named logger. -->
<!ELEMENT logger (param*,level?,appender-ref*)>
<!ATTLIST logger
  class         CDATA   #IMPLIED
  name CDATA #REQUIRED
  additivity (true|false) "true"
>
<!ELEMENT categoryFactory (param*)>
<!ATTLIST categoryFactory
  class        CDATA #REQUIRED>

<!ELEMENT loggerFactory (param*)>
<!ATTLIST loggerFactory
  class        CDATA #REQUIRED>

<!ELEMENT appender-ref EMPTY>
<!ATTLIST appender-ref
  ref CDATA #REQUIRED
>

<!-- plugins must have a name and class and can have optional parameters -->
<!ELEMENT plugin (param*, connectionSource?)>
<!ATTLIST plugin
  name CDATA    #REQUIRED
  class CDATA  #REQUIRED
>

<!ELEMENT connectionSource (dataSource?, param*)>
<!ATTLIST connectionSource
  class        CDATA  #REQUIRED
>

<!ELEMENT dataSource (param*)>
<!ATTLIST dataSource
  class        CDATA  #REQUIRED
>

<!ELEMENT triggeringPolicy ((param|filter)*)>
<!ATTLIST triggeringPolicy
  name CDATA  #IMPLIED
  class CDATA  #REQUIRED
>

<!ELEMENT rollingPolicy (param*)>
<!ATTLIST rollingPolicy
  name CDATA  #IMPLIED
  class CDATA  #REQUIRED
>
<!-- If no priority element is specified, then the configurator MUST not -->
<!-- touch the priority of root. -->
<!-- The root category always exists and cannot be subclassed. -->
<!ELEMENT root (param*, (priority|level)?, appender-ref*)>
<!-- ==================================================================== -->
<!--                       A logging event                                -->
<!-- ==================================================================== -->
<!ELEMENT log4j:eventSet (log4j:event*)>
<!ATTLIST log4j:eventSet
  xmlns:log4j             CDATA #FIXED "http://jakarta.apache.org/log4j/"
  version                (1.1|1.2) "1.2"
  includesLocationInfo   (true|false) "true"
>

<!ELEMENT log4j:event (log4j:message, log4j:NDC?, log4j:throwable?,
  log4j:locationInfo?, log4j:properties?) >

<!-- The timestamp format is application dependent. -->
<!ATTLIST log4j:event
  logger     CDATA #REQUIRED
  level      CDATA #REQUIRED
  thread     CDATA #REQUIRED
  timestamp  CDATA #REQUIRED
  time       CDATA #IMPLIED
>

<!ELEMENT log4j:message (#PCDATA)>
<!ELEMENT log4j:NDC (#PCDATA)>

<!ELEMENT log4j:throwable (#PCDATA)>

<!ELEMENT log4j:locationInfo EMPTY>
<!ATTLIST log4j:locationInfo
  class  CDATA #REQUIRED
  method CDATA #REQUIRED
  file   CDATA #REQUIRED
  line   CDATA #REQUIRED
>

<!ELEMENT log4j:properties (log4j:data*)>

<!ELEMENT log4j:data EMPTY>
<!ATTLIST log4j:data
  name   CDATA #REQUIRED
  value  CDATA #REQUIRED
>

一个具体的demo:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
  <!-- Appender的设置 -->
    <appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender">
      <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d %-5p [%t] [%F:%L] [%X] - %m%n" />
      </layout>
  </appender>
  <appender name="FileAppender" class="org.apache.log4j.FileAppender">
    <param name="File" value="/tmp/log.log" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %-5p [%t] [%F:%L] [%X] - %m%n" />
    </layout>
  </appender>
  <appender name="RewriteAppender" class="org.apache.log4j.rewrite.RewriteAppender">
    <appender-ref ref="FileAppender" />
    <appender-ref ref="ConsoleAppender" />
    <rewritePolicy class="org.apache.log4j.rewrite.PropertyRewritePolicy">
      <param name="Properties" value="name=yx,like=nju" />
    </rewritePolicy>
  </appender>
  <!-- 根logger的设置 -->
  <root>
    <priority value="debug" />
    <appender-ref ref="RewriteAppender" />
  </root>
</log4j:configuration>

4.2 DOMConfigurator实现

DOMConfigurator类结构,具体和PropertyConfigurator整体比较类似,主要是配置文件格式引起的解析流程区别。

http://svn.apache.org/viewvc/logging/log4j/trunk/src/main/java/org/apache/log4j/LogManager.java?revision=1343042&view=co

DOMConfigurator

通过下面常量大致知道xml模式下,配置项支持或包含哪些内容:

  static final String CONFIGURATION_TAG = "log4j:configuration";
  static final String OLD_CONFIGURATION_TAG = "configuration";
  static final String RENDERER_TAG      = "renderer";
  private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
  static final String APPENDER_TAG = "appender";
  static final String APPENDER_REF_TAG = "appender-ref";  
  static final String PARAM_TAG    = "param";
  static final String LAYOUT_TAG = "layout";
  static final String CATEGORY = "category";
  static final String LOGGER = "logger";
  static final String LOGGER_REF = "logger-ref";
  static final String CATEGORY_FACTORY_TAG  = "categoryFactory";
  static final String LOGGER_FACTORY_TAG  = "loggerFactory";
  static final String NAME_ATTR = "name";
  static final String CLASS_ATTR        = "class";
  static final String VALUE_ATTR = "value";
  static final String ROOT_TAG = "root";
  static final String ROOT_REF = "root-ref";
  static final String LEVEL_TAG         = "level";
  static final String PRIORITY_TAG      = "priority";
  static final String FILTER_TAG = "filter";
  static final String ERROR_HANDLER_TAG = "errorHandler";
  static final String REF_ATTR = "ref";
  static final String ADDITIVITY_ATTR    = "additivity";  
  static final String THRESHOLD_ATTR       = "threshold";
  static final String CONFIG_DEBUG_ATTR  = "configDebug";
  static final String INTERNAL_DEBUG_ATTR  = "debug";
  private static final String RESET_ATTR  = "reset";
  static final String RENDERING_CLASS_ATTR = "renderingClass";
  static final String RENDERED_CLASS_ATTR = "renderedClass";

  static final String EMPTY_STR = "";
  static final Class[] ONE_STRING_PARAM = new Class[] {String.class};

  final static String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";

xml解析调用入口:

dbf.setValidating(true);
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
docBuilder.setErrorHandler(new SAXErrorHandler());      
docBuilder.setEntityResolver(new Log4jEntityResolver());

Document doc = action.parse(docBuilder);     
parse(doc.getDocumentElement());

parse核心逻辑:

  protected void parse(Element element) {
    String rootElementName = element.getTagName();
    // log4j:configuration 或者 configuration
    if (!rootElementName.equals(CONFIGURATION_TAG)) {
      if(rootElementName.equals(OLD_CONFIGURATION_TAG)) {
        LogLog.warn("The <"+OLD_CONFIGURATION_TAG+"> element has been deprecated.");
        LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead.");
      } else {
        LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element.");
        return;
      }
    }
    // 是否debug模式,即在root元素中配置debug属性
    String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
      
    LogLog.debug("debug attribute= \"" + debugAttrib +"\".");
    // if the log4j.dtd is not specified in the XML file, then the
    // "debug" attribute is returned as the empty string.
    if(!debugAttrib.equals("") && !debugAttrib.equals("null")) {      
      LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
    } else {
      LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
    }

    // 是否设置reset属性
    String resetAttrib = subst(element.getAttribute(RESET_ATTR));
    if(!("".equals(resetAttrib))) {
      if (OptionConverter.toBoolean(resetAttrib, false)) {
        repository.resetConfiguration();
      }
    }
    // configDebug
    String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
    if(!confDebug.equals("") && !confDebug.equals("null")) {
      LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
    }
    // threshold
    String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
    if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
      repository.setThreshold(thresholdStr);
    }

    //Hashtable appenderBag = new Hashtable(11);
    /* Building Appender objects, placing them in a local namespace
       for future reference */

    // First configure each category factory under the root element.
    // Category factories need to be configured before any of
    // categories they support.
    String   tagName = null;
    Element  currentElement = null;
    Node     currentNode = null;
    NodeList children = element.getChildNodes();
    final int length = children.getLength();

    for (int loop = 0; loop < length; loop++) {
      currentNode = children.item(loop);
      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
        currentElement = (Element) currentNode;
        tagName = currentElement.getTagName();
        // 对于每一个子节点,是否是日志记录器工厂,即判断categoryFactory、loggerFactory
        if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
          parseCategoryFactory(currentElement);
        }
      }
    }
    
    for (int loop = 0; loop < length; loop++) {
      currentNode = children.item(loop);
      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
        currentElement = (Element) currentNode;
        tagName = currentElement.getTagName();
        // 对于每一个子节点,是否是日志记录器category、logger
        if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
          parseCategory(currentElement);
        } else if (tagName.equals(ROOT_TAG)) {
          //root
          parseRoot(currentElement);
        } else if(tagName.equals(RENDERER_TAG)) {
          //renderer
          parseRenderer(currentElement);
        } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {
          //throwableRenderer
          if (repository instanceof ThrowableRendererSupport) {
            ThrowableRenderer tr = parseThrowableRenderer(currentElement);
            if (tr != null) {
              ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
            }
          }
        } else if (!(tagName.equals(APPENDER_TAG)
            || tagName.equals(CATEGORY_FACTORY_TAG)
            || tagName.equals(LOGGER_FACTORY_TAG))) {
          // 用户可以添加自己的UnrecognizedElementHandler实现逻辑,去处理log4j框架不识别的一些配置项目
          quietParseUnrecognizedElement(repository, currentElement, props);
        }
      }
    }
  }


  protected void parseCategory (Element loggerElement) {
    // Create a new org.apache.log4j.Category object from the category element.
    //name 属性是日志记录器的名字
    String catName = subst(loggerElement.getAttribute(NAME_ATTR));
    Logger cat; 
    // class属性是日志记录器的实现类名,通过反射生成其实例
    String className = subst(loggerElement.getAttribute(CLASS_ATTR));
    if(EMPTY_STR.equals(className)) {
      LogLog.debug("Retreiving an instance of org.apache.log4j.Logger.");
      cat = (catFactory == null) ? repository.getLogger(catName) : repository.getLogger(catName, catFactory);
    }
    else {
      LogLog.debug("Desired logger sub-class: ["+className+']');
       try {  
         Class clazz = Loader.loadClass(className);
         Method getInstanceMethod = clazz.getMethod("getLogger", ONE_STRING_PARAM);
         cat = (Logger) getInstanceMethod.invoke(null, new Object[] {catName});
       } catch (InvocationTargetException oops) {
          if (oops.getTargetException() instanceof InterruptedException
                || oops.getTargetException() instanceof InterruptedIOException) {
            Thread.currentThread().interrupt();
          }
          LogLog.error("Could not retrieve category ["+catName+"]. Reported error follows.", oops);
          return;
       } catch (Exception oops) {
         LogLog.error("Could not retrieve category ["+catName+"]. Reported error follows.", oops);
         return;
       }
    }

    // Setting up a category needs to be an atomic operation, in order
    // to protect potential log operations while category
    // configuration is in progress.
    synchronized(cat) {
      boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)),
true);
    
      LogLog.debug("Setting ["+cat.getName()+"] additivity to ["+additivity+"].");
      cat.setAdditivity(additivity);
      parseChildrenOfLoggerElement(loggerElement, cat, false);
    }
  }


  protected Appender parseAppender (Element appenderElement) {
    String className = subst(appenderElement.getAttribute(CLASS_ATTR));
    LogLog.debug("Class name: [" + className+']');    
    try {
      Object instance = Loader.loadClass(className).newInstance();
      Appender appender = (Appender)instance;
      PropertySetter propSetter = new PropertySetter(appender);
      appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
      NodeList children = appenderElement.getChildNodes();
      final int length = children.getLength();

      for (int loop = 0; loop < length; loop++) {
        Node currentNode = children.item(loop);
        /* We're only interested in Elements */
        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
          Element currentElement = (Element)currentNode;
          // Parse appender parameters 
          if (currentElement.getTagName().equals(PARAM_TAG)) {
            setParameter(currentElement, propSetter);
          }
          // Set appender layout
          else if (currentElement.getTagName().equals(LAYOUT_TAG)) {
            appender.setLayout(parseLayout(currentElement));
          }
          // Add filters
          else if (currentElement.getTagName().equals(FILTER_TAG)) {
            parseFilters(currentElement, appender);
          }
          else if (currentElement.getTagName().equals(ERROR_HANDLER_TAG)) {
            parseErrorHandler(currentElement, appender);
          }
          else if (currentElement.getTagName().equals(APPENDER_REF_TAG)) {
            String refName = subst(currentElement.getAttribute(REF_ATTR));
            if(appender instanceof AppenderAttachable) {
              AppenderAttachable aa = (AppenderAttachable) appender;
              LogLog.debug("Attaching appender named ["+ refName+"] to appender named ["+ appender.getName()+"].");
              aa.addAppender(findAppenderByReference(currentElement));
            } else {
              LogLog.error("Requesting attachment of appender named ["+refName+ "] to appender named ["+ appender.getName()+"] which does not implement org.apache.log4j.spi.AppenderAttachable.");
            }
          } else {
            parseUnrecognizedElement(instance, currentElement, props);
          }
        }
      }
      propSetter.activate();
      return appender;
    }
    /* Yes, it's ugly.  But all of these exceptions point to the same
       problem: we can't create an Appender */
    catch (Exception oops) {
      if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
        Thread.currentThread().interrupt();
      }
      LogLog.error("Could not create an Appender. Reported error follows.",oops);
      return null;
    }
  }
进一步的解析是类似的过程...

4.3 一点区别

DOMConfigurator功能上不同于PropertyConfigurator一个功能点是DOMConfigurator支持log4j不认识的一些配置项,用户可以添加自己的UnrecognizedElementHandler实现逻辑,去处理log4j框架不识别的一些配置项目。

  private static void parseUnrecognizedElement(final Object instance,
                 final Element element,
                 final Properties props) throws Exception {
      boolean recognized = false;
      if (instance instanceof UnrecognizedElementHandler) {
          recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(element, props);
      }
      if (!recognized) {
          LogLog.warn("Unrecognized element " + element.getNodeName());
      }
  }

DOMConfigurator有看门狗支持,具体间隔一定时间,检查配置文件的最新修改时间,如果有修改变动则进行log4j配置体系的重新配置:

  static public
  void configureAndWatch(String configFilename) {
    configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
  }
  static public
  void configureAndWatch(String configFilename, long delay) {
    XMLWatchdog xdog = new XMLWatchdog(configFilename);
    xdog.setDelay(delay);
    xdog.start();
  }

class XMLWatchdog extends FileWatchdog {
  XMLWatchdog(String filename) {
    super(filename);
  }
  // Call DOMConfigurator#configure(String) with the filename to reconfigure log4j
  public void doOnChange() {
    new DOMConfigurator().doConfigure(filename, 
      LogManager.getLoggerRepository());
  }
}
诸如此类的...

标签: none
评论列表
  1. 便是这个留言板 能够设置成 显现最新的顺序

添加新评论