本篇文章中涉及到的所有代码都已经上传到gitee中: gitee.com/sss123a/log...
Hello world!
引入maven依赖:
            
            
              xml
              
              
            
          
          <dependency>  
    <groupId>log4j</groupId>  
    <artifactId>log4j</artifactId>  
    <version>1.2.17</version>  
</dependency>
        第一个log4j程序:
            
            
              java
              
              
            
          
          import org.apache.log4j.Logger;  
  
public class HelloWorld {  
    public static void main(String[] args) {  
        Logger logger = Logger.getLogger(HelloWorld.class);  
        logger.info("Hello world!");  
    }  
}
        输出结果如下: 
 这里红色提示其实是log4j内部使用LogLog工具类打印的,我们可以调用LogLog.setQuietMode(true); 来禁用log4j内部一切日志输出。具体可以参考LogLog类中的静态代码块支持更强大的功能配置。
那至于为什么log4j要输出这样的提示信息,那是因为还缺少对Logger的进一步配置。 比如我们可以调用BasicConfigurator.configure(); 。其本质是为该logger对象增加了一个ConsoleAppender,它负责将日志输出到console。原理就等同于jul中的java.util.logging.ConsoleHandler类。具体代入如下:
            
            
              java
              
              
            
          
          package com.matio.log4j.helloworld;  
  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.Logger;  
  
public class HelloWorld {  
    public static void main(String[] args) {  
        BasicConfigurator.configure();  
        Logger logger = Logger.getLogger(HelloWorld.class);  
        logger.info("Hello world!");  
    }  
}
        另外,log4j默认支持xml格式和properties格式的配置文件,对应log4j中的两个工具类:
- DOMConfigurator:解析xml文件
 - PropertyConfigurator 解析properties文件
 
接下来我们以xml配置文件为例来配置我们的log4j:
1.如果xml文件在在resources目录下,且文件名为log4j.xml,那么log4j可以自动读取该文件,具体可以参考org.apache.log4j.LogManager的静态代码块
2.如果xml文件在resources目录下:
            
            
              java
              
              
            
          
           DOMConfigurator.configure(Loader.getResource("log4j-asyncappender.xml"))
        3.如果xml文件在磁盘上任意位置:
            
            
              java
              
              
            
          
          DOMConfigurator.configure("E:/WorkspaceIdea/01src/log/matio-log4j/src/main/resources/log4j-asyncappender.xml");
        4.也可以把xml文件放到一个指定的位置,并且使用环境变量log4j.configuration 来完成配置文件的指定。注意,在log4j.configuration的值中,可以使用文件名称或者url的方式。比如:
log4j.configuration=log4jconfig.properties (resources目录下) log4j.configuration=file:/c:/log4jconfig.xml
log4j.xml配置文件解析
            
            
              java
              
              
            
          
          package org.apache.log4j.spi;  
  
import java.io.InputStream;  
import java.net.URL;  
public interface Configurator {  
    void doConfigure(InputStream inputStream, LoggerRepository repository);  
    void doConfigure(URL url, LoggerRepository repository);  
}
        该接口主要有两个核心实现类
- DOMConfigurator:解析xml文件
 - PropertyConfigurator 解析properties文件
 
两者实现方式大同小异,我们以xml文件为例解剖log4j内部实现:
            
            
              xml
              
              
            
          
          <?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration  
debug="${matio.log4j.debug}"  
reset="${matio.log4j.reset}"  
threshold="${matio.log4j.threshold}"  
xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <!-- debug:同configDebug标签: LogLog.setInternalDebugging(boolean)-->  
    <!-- reset:LoggerRepository.resetConfiguration()-->  
    <!-- threshold:设置LoggerRepository#setThreshold(String) -->  
    <!-- loggerFactory:同categoryFactory标签,可以同时存在多个,但是最后一个才生效 -->  
    <loggerFactory class="${matio.log4j.loggerFactory.class}">  
        <!-- param属性会通过反射注入到class对象中 -->  
        <param name="p1" value="v1"/>  
        <param name="p2" value="v2"/>  
        <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
        <age>30</age>  
        <name value="matio"/>  
    </loggerFactory>  
    <!-- appender: name和class属性必须-->  
    <appender name="myConsole" class="org.apache.log4j.ConsoleAppender">  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <!--过滤器设置输出的级别-->  
        <filter class="org.apache.log4j.varia.LevelRangeFilter">  
            <param name="levelMin" value="debug"/>  
            <param name="levelMax" value="warn"/>  
            <param name="acceptOnMatch" value="true"/>  
        </filter>  
    </appender>  
    <appender name="myFile" class="org.apache.log4j.RollingFileAppender">  
        <param name="File" value="D:/output.log"/><!-- 设置日志输出文件名 -->  
        <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->  
        <param name="Append" value="true"/>  
        <param name="MaxBackupIndex" value="10"/>  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="%p (%c:%L)- %m%n"/>  
        </layout>  
    </appender>  
    <appender name="activexAppender" class="org.apache.log4j.DailyRollingFileAppender">  
        <param name="File" value="E:/activex.log"/>  
        <param name="DatePattern" value="'.'yyyy-MM-dd'.log'"/>  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{MMdd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n"/>  
        </layout>  
    </appender>  
    <!-- 指定logger的设置,additivity指示是否遵循缺省的继承机制-->  
    <!-- logger:同category标签-->  
    <logger name="com.runway.bssp.activeXdemo" additivity="false">  
        <!-- level:同priority, -->  
        <level value="info"/>  
        <!-- appender-ref:可以存在多个,开始解析目标appender了, -->  
        <appender-ref ref="activexAppender"/>  
        <appender-ref ref="myFile"/>  
    </logger>  
    <logger name="com.matio.test1" class="com.matio.log4j.logger.LoggerUtil" additivity="false">  
        <priority value="info" class="com.matio.log4j.level.CusLevel"/>  
    </logger>  
    <!-- renderer:可以存在多个 -->  
    <!-- 如果打印的日志message类型是User,就调用CusObjectRenderer.doRender()二次处理生成新的message -->  
    <renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer"  
    renderedClass="com.matio.log4j.User"/>  
    <renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer2"  
    renderedClass="com.matio.log4j.Test1"/>  
    <!-- throwableRenderer:class类需要实现ThrowableRenderer接口 -->  
    <throwableRenderer class="com.matio.log4j.throwablerenderer.CusThrowableRenderer">  
        <!-- param属性会通过反射注入到当前class对象中 -->  
        <param name="p100" value="v100"/>  
        <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
        <lover>zcf</lover>  
    </throwableRenderer>  
    <!-- 根logger的设置-->  
    <root>  
        <priority value="debug"/>  
        <appender-ref ref="myConsole"/>  
        <appender-ref ref="myFile"/>  
    </root>
  
</log4j:configuration>
        根节点:log4j:configuration
DOMConfigurator#parse(Element)
用法如下:
            
            
              xml
              
              
            
          
          <?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration  
    debug="${matio.log4j.debug}"  
    reset="${matio.log4j.reset}"  
    threshold="${matio.log4j.threshold}"  
    xmlns:log4j='http://jakarta.apache.org/log4j/'>
    
</log4j:configuration>    
        重点: 可以通过${}从系统属性中获取,也可以固定写死
| 类型 | 描述 | 默认值 | |
|---|---|---|---|
| log4j:configuration | 根标签 | 同configuration | |
| debug | 属性 | 是否打印log4j内部debug日志,同configDebug,LogLog.setInternalDebugging(boolean); | false | 
| reset | 属性 | 如果为true则调用LoggerRepository.resetConfiguration(); | |
| threshold | 属性 | 默认的日志level,LoggerRepository.setThreshold(); | Level.ALL | 
loggerFactory同categoryFactory:自定义LoggerFactory
如果配置了该标签,那么该xml文件中所有logger都会通过该loggerFactory去生成,不走默认的了
DOMConfigurator#parseCategoryFactory(Element)
可以同时存在多个,但是最后一个才生效
            
            
              xml
              
              
            
          
          <!-- loggerFactory:同categoryFactory标签,可以同时存在多个,但是最后一个才生效 -->  
<loggerFactory class="${matio.log4j.loggerFactory.class}">  
    <!-- param属性会通过反射注入到class对象中 -->  
    <param name="p1" value="v1"/>  
    <param name="p2" value="v2"/>  
    <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
    <age>30</age>  
    <name value="matio"/>  
</loggerFactory>
        | 类型 | 描述 | |
|---|---|---|
| loggerFactory | 标签 | 用户可以自定义LoggerFactory,同categoryFactory标签 | 
| class | 属性 | LoggerFactory具体实现类,如果没有设置class属性,则直接跳过该标签 | 
| param | 标签 | 通过反射将value设置给class类中的属性 | 
| param.name | 属性 | class类中的属性名称 | 
| param.value | 属性 | class类中的属性值 | 
class属性:可以通过${}从系统属性中获取,比如
System.setProperty("matio.log4j.loggerFactory.class", "com.matio.log4j.loggerfactory.CusLoggerFactory");
也可以固定写死,比如class="com.matio.log4j.loggerfactory.CusLoggerFactory"
备注:如果class实现类实现了UnrecognizedElementHandler接口,那么我们还可以解析自定义xml标签,示例代码:
            
            
              java
              
              
            
          
          package com.matio.log4j.loggerfactory;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.spi.LoggerFactory;  
import org.apache.log4j.xml.DOMConfigurator;  
import org.apache.log4j.xml.UnrecognizedElementHandler;  
import org.w3c.dom.Element;  
  
import java.util.Properties;  
  
public class CusLoggerFactory implements LoggerFactory, UnrecognizedElementHandler {  
  
    private String p1; // <param name="p1" value="v1"/>  
    private String p2; // <param name="p2" value="v2"/>  
    private int age;  
    private String name;  
    public Logger makeNewLoggerInstance(String name) {  
        return Logger.getLogger(name);  
    }  
    public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {  
        String nodeName = element.getNodeName();  
        if ("age".equals(nodeName)) { // 解析:<age>30</age>  
            String ageStr = DOMConfigurator.subst(element.getNodeValue(), props);  
            if (ageStr != null) {  
                age = Integer.parseInt(ageStr);  
            }  
        } else if ("name".equals(nodeName)) { // 解析:<name value="matio"/>  
            name = DOMConfigurator.subst(element.getAttribute("value"), props);  
        }  
        return true;  
    }  
    // 忽略getter()和setter()...
}
        logger同category
定义一个logger对象
DOMConfigurator#parseCategory(Element)
可以同时存在多个logger标签
            
            
              xml
              
              
            
          
          <logger name="com.runway.bssp.activeXdemo" additivity="false">  
    <!-- level:同priority, -->  
    <level value="info"/>  
    <!-- appender-ref:可以存在多个,开始解析目标appender了, -->  
    <appender-ref ref="activexAppender"/>  
    <appender-ref ref="myFile"/>  
</logger>  
  
<logger name="com.matio.test1" class="com.matio.log4j.logger.LoggerUtil" additivity="false">  
    <priority value="info" class="com.matio.log4j.level.CusLevel"/>  
</logger>
        | 类型 | 描述 | |
|---|---|---|
| logger | 标签 | 创建一个logger对象,同category标签 | 
| class | 属性 | 通过该类名反射调用其内部的getLogger()方法去生成logger对象 | 
| name | 属性 | 该logger的name | 
| additivity | 属性 | 是否遵循缺省的继承机制,默认true | 
| appender-ref | 标签 | appender的name,这个时候才开始去解析目标appender标签 | 
| level | 标签 | 该logger的日志level,可以通过class属性自定义level | 
| priority | 标签 | 同level | 
| param | 标签 | 通过反射将value设置给class类中的属性 | 
| param.name | 属性 | class类中的属性名称 | 
| param.value | 属性 | class类中的属性值 | 
在每个logger标签被解析完成后,如果发现该logger类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
root
只能存在一个root标签
            
            
              xml
              
              
            
          
          <!-- 根logger的设置-->  
<root>  
    <priority value="debug"/>  
    <appender-ref ref="myConsole"/>  
    <appender-ref ref="myFile"/>  
</root>
        | 类型 | 描述 | |
|---|---|---|
| appender-ref | 标签 | appender的name,这个时候才开始去解析目标appender标签 | 
| level | 标签 | 该logger的日志level,可以通过class属性自定义level | 
| priority | 标签 | 同level | 
| param | 标签 | 通过反射将value设置给class类中的属性 | 
| param.name | 属性 | class类中的属性名称 | 
| param.value | 属性 | class类中的属性值 | 
在root标签被解析完成后,如果发现该logger类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
renderer
不知道大家有没有注意到logger.info();方法接收的参数类型是什么.其实都是Object类型(jul接受的都是string类型),这说明log4j可以打印任意类型的日志,比如:
            
            
              java
              
              
            
          
          BasicConfigurator.configure(); // 日志输出到console上
Logger logger = Logger.getLogger("test");
logger.info(new User("best", "zcf", "matio"));  
logger.info(new Test1("aaaa"));
        以上两行代码打印结果如下:
            
            
              js
              
              
            
          
          0 [main] INFO test  - User{school='best', like='zcf', name='matio'}
1 [main] INFO test  - Test1{x='aaaa'}
        我们都知道输出一个对象其实就是输出其string()方法,但是log4j提供了一个工具来用户根据指定的日志对象类型输出对应的日志,这个工具就是org.apache.log4j.or。ObjectRenderer,其接口定义是如下:
            
            
              java
              
              
            
          
          public interface ObjectRenderer {  
 String doRender(Object o);  
}
        参数o就是我们上面的User对象和Test1对象,返回值就是最终要打印的日志结果
这里为了更好的理解什么是ObjectRenderer,附带了一个DEMO,它不需要xml配置文件,示例如下:
            
            
              java
              
              
            
          
          package com.matio.log4j.objectrenderer;  
  
import com.matio.log4j.Test1;  
import com.matio.log4j.User;  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.LogManager;  
import org.apache.log4j.Logger;  
import org.apache.log4j.or.RendererMap;  
import org.apache.log4j.spi.RendererSupport;  
  
public class ObjectRendererTest {  
    public static void main(String[] args) {  
        // 注册一个consoleAppender,等会日志打印在控制台上  
        BasicConfigurator.configure();  
        // 注册一个ObjectRenderer专门用于处理User类型的消息  
        {  
            // message的class  
            String renderedClassName = "com.matio.log4j.User";  
            // ObjectRenderer实现类  
            String renderingClassName = "com.matio.log4j.objectrenderer.CusObjectRenderer";  
            RendererMap.addRenderer((RendererSupport) LogManager.getLoggerRepository(), renderedClassName, renderingClassName);  
        }  
        // 再注册一个ObjectRenderer专门用于处理Test1类型的消息  
        {  
            // message的class  
            String renderedClassName = "com.matio.log4j.Test1";  
            // ObjectRenderer实现类  
            String renderingClassName = "com.matio.log4j.objectrenderer.CusObjectRenderer2";  
            RendererMap.addRenderer((RendererSupport) LogManager.getLoggerRepository(), renderedClassName, renderingClassName);  
        }  
        Logger logger = Logger.getLogger("test");  
        logger.info(new User("best", "zcf", "matio"));  
        logger.info(new Test1("aaaa"));  
    }  
}
        自定义ObjectRenderer,用于处理User类型和Test1类型的消息:
            
            
              java
              
              
            
          
          package com.matio.log4j.objectrenderer;  
  
import com.matio.log4j.User;  
import org.apache.log4j.or.ObjectRenderer;  
import org.apache.log4j.xml.UnrecognizedElementHandler;  
import org.w3c.dom.Element;  
import java.util.Properties;  
  
public class CusObjectRenderer implements ObjectRenderer, UnrecognizedElementHandler {  
  
    public String doRender(Object o) {  
        if (o instanceof User) {  
            User user = (User) o;  
            return "测试ObjectRenderer : " + user.toString();  
        }  
        return o.toString();  
    }  
    // 这个只在xml配置生效  
    public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {  
        return true;  
    }  
}
public class CusObjectRenderer2 implements ObjectRenderer {  
  
    public String doRender(Object o) {  
        Test1 test1 = (Test1) o;  
        return "测试ObjectRenderer222 : " + test1.toString();  
    }  
}
        其打印结果如下:
            
            
              js
              
              
            
          
          0 [main] INFO test  - 测试ObjectRenderer : User{school='best', like='zcf', name='matio'}
0 [main] INFO test  - 测试ObjectRenderer222 : Test1{x='aaaa'}
        那么在xml文件中我们该如何配置renderer呢,具体可以参考:
DOMConfigurator#parseRenderer(Element);
renderer在xml中的配置如下,支持多个:
            
            
              xml
              
              
            
          
          <!-- renderer:可以存在多个 -->  
<!-- 如果打印的日志message类型是User,就调用CusObjectRenderer.doRender()二次处理生成新的message -->  
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer"  
renderedClass="com.matio.log4j.User"/>
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer2"  
renderedClass="com.matio.log4j.Test1"/>
        | 类型 | 描述 | |
|---|---|---|
| renderer | 标签 | 真的指定类型的日志,就调用CusObjectRenderer.doRender()二次处理生成新的日志 | 
| renderingClass | 属性 | ObjectRenderer的实现类 | 
| renderedClass | 属性 | 日志类型 | 
throwableRenderer
什么是throwableRenderer?
它可以将Throwable转成String,对应log4j中的接口定义如下:
            
            
              java
              
              
            
          
          package org.apache.log4j.spi;  
public interface ThrowableRenderer {  
    public String[] doRender(Throwable t);  
}
        log4j默认使用DefaultThrowableRenderer
没有注册自定义ThrowableRenderer之前,实例代码如下:
            
            
              java
              
              
            
          
          package com.matio.log4j.throwablerenderer;  
  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.LogManager;  
import org.apache.log4j.Logger;  
import org.apache.log4j.spi.ThrowableRendererSupport;  
  
public class TestThrowableRenderer {  
  
    public static void main(String[] args) {  
        BasicConfigurator.configure(); // 日志输出到console上  
        Logger logger = Logger.getLogger("Te");  
        logger.error("错误xxx:", new IllegalArgumentException());  
        logger.error("错误:");  
    }  
}
        输出结果如下:
            
            
              js
              
              
            
          
          1 [main] ERROR Te  - 错误xxx:
java.lang.IllegalArgumentException
	at com.matio.log4j.throwablerenderer.TestThrowableRenderer.main(TestThrowableRenderer.java:17)
3 [main] ERROR Te  - 错误:
        当注册一个自定义的ThrowableRenderer后代码如下:
            
            
              java
              
              
            
          
          package com.matio.log4j.throwablerenderer;  
  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.LogManager;  
import org.apache.log4j.Logger;  
import org.apache.log4j.spi.ThrowableRendererSupport;  
  
public class TestThrowableRenderer {  
  
    public static void main(String[] args) {  
        BasicConfigurator.configure(); // 日志输出到console上  
        // 注册一个自定义的ThrowableRenderer  
        {  
            CusThrowableRenderer renderer = new CusThrowableRenderer();  
            ((ThrowableRendererSupport) LogManager.getLoggerRepository()).setThrowableRenderer(renderer);  
        }  
        Logger logger = Logger.getLogger("Te");  
        logger.error("错误xxx:", new IllegalArgumentException());  
        logger.error("错误:");  
    }  
}
        
            
            
              java
              
              
            
          
          package com.matio.log4j.throwablerenderer;  
  
import org.apache.log4j.spi.OptionHandler;  
import org.apache.log4j.spi.ThrowableRenderer;  
import org.apache.log4j.xml.DOMConfigurator;  
import org.apache.log4j.xml.UnrecognizedElementHandler;  
import org.w3c.dom.Element;  
  
import java.io.PrintWriter;  
import java.io.StringWriter;  
import java.util.Properties;  
  
public class CusThrowableRenderer implements ThrowableRenderer, OptionHandler, UnrecognizedElementHandler {  
  
    private String p100;    
    private String lover;  
  
    public String[] doRender(Throwable t) {  
        StringWriter stringWriter = new StringWriter();  
        PrintWriter printWriter = new PrintWriter(stringWriter);  
        t.printStackTrace(printWriter);  
        printWriter.close();  
        String[] strings = new String[1];  
        strings[0] = "自定义ThrowableRenderer: " + stringWriter.toString();  
        return strings;  
    }  
    public void activateOptions() {  
    }  
    // 这里只在xml配置中生效  
    public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {  
        this.lover = DOMConfigurator.subst(element.getNodeValue(), props);  
        return true;  
    }  
  
    // 省略getter()和setter()...  
}
        输出结果如下:
            
            
              js
              
              
            
          
          0 [main] ERROR Te  - 错误xxx:
自定义ThrowableRenderer: java.lang.IllegalArgumentException
	at com.matio.log4j.throwablerenderer.TestThrowableRenderer.main(TestThrowableRenderer.java:19)
2 [main] ERROR Te  - 错误:
        那么在xml中如何声明throwableRenderer呢,具体参考
DOMConfigurator#parseThrowableRenderer(Element);
            
            
              xml
              
              
            
          
          <!-- throwableRenderer:class类需要实现ThrowableRenderer接口 -->  
<throwableRenderer class="com.matio.log4j.throwablerenderer.CusThrowableRenderer">  
    <!-- param属性会通过反射注入到当前class对象中 -->  
    <param name="p100" value="v100"/>  
    <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
    <lover>zcf</lover>  
</throwableRenderer>
        | 类型 | 描述 | |
|---|---|---|
| throwableRenderer | 标签 | 自定义ThrowableRenderer | 
| class | 属性 | ThrowableRendere的实现类 | 
| param | 标签 | 通过反射将value设置给class类中的属性 | 
| param.name | 属性 | class类中的属性名称 | 
| param.value | 属性 | class类中的属性值 | 
在throwableRenderer标签被解析完成后,如果发现该throwableRenderer类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
appender
每一个handler标签都会被实例化成一个Appender对象(它其实就等同于jul中的Handler),负责将日志输出到console上、保存到文件中,保存到数据库中、或者通过socket发送给远端等

DOMConfigurator#parseAppender(Element);
            
            
              xml
              
              
            
          
          <!-- appender: name和class属性必须-->  
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">  
    <layout class="org.apache.log4j.PatternLayout">  
        <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
    </layout>  
    <!--过滤器设置输出的级别-->  
    <filter class="org.apache.log4j.varia.LevelRangeFilter">  
        <param name="levelMin" value="debug"/>  
        <param name="levelMax" value="warn"/>  
        <param name="acceptOnMatch" value="true"/>  
    </filter>  
    
    <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 --> 
    <age>30</age> 
    <name value="matio"/>
</appender>
        | 类型 | 描述 | |
|---|---|---|
| appender | 标签 | 对应jul中的handler对象 | 
| class | 属性 | Appender实现类 | 
| name | 属性 | 该appender的name | 
| param | 标签 | 通过反射将value设置给class类中的属性 | 
| param.name | 属性 | class类中的属性名称 | 
| param.value | 属性 | class类中的属性值 | 
| layout | 标签 | 定义该appender的日志输出样式,见parseLayout() | 
| filter | 标签 | 定义该appender的日志过滤器,见parseFilters() | 
| errorHandler | 标签 | 输出日志时如果出现异常就交给该handler处理,见parseErrorHandler() | 
| appender-ref | 标签 | 只有该appender实现了AppenderAttachable接口才有效,就是代表真正的appender,可以有多个 | 
在每个appender标签被解析完成后,如果发现该appender类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
最后再添加到对应的logger中