Log4J学习【六】Log4J的体系结构之Appender的结构

时间:2025-03-23 09:13:42
使用Logger的日志记录方法,仅仅是发出了日志记录的事件,具体日志要记录到什么地方,需要Appender的支持。在Log4J中,Appender定义了日志输出的目的地。在上面所有的示例当中,我们日志输出的目的地都是控制台,在Log4j中,还有非常多的Appender可供选择,可以将日志输出到文件,网络,数据库等等,这个后面再介绍。说到这里,可能有人就已经会思考,既然Logger对象的info()等方法仅仅是发出了日志记录的事件,还需要指定输出目的地;那么我们之前的示例代码也并没有为任何一个Logger设置Appender啊?其实这很好理解,我们回顾一下之前的Level,按道理,也应该为每一个Logger指定对应的日志输出级别,但是我们也并没有这样做,正是因为Logger本身存在一个完整的体系结构,而Level能够在这个结构中自下而上的继承。同理,Appender也具有这种继承的特性。下面给出一段代码,先看看怎么使用代码的方式指定Appender:
@Test
public void testLogAppender1(){
    Logger log1=("cd");
     ();
    (new ConsoleAppender(new SimpleLayout()));
    Logger log2=("");
    ("log2 info");
    ("log2 debug");
}

    注意在这段代码中的加粗的代码。第一句代码设置了日志级别为DEBUG;第二条代码,调用了Logger的addAppender方法添加了一个ConsoleAppender;从类的名字上看就知道这是一个把日志输出到控制台上的Appender,在创建ConsoleAppender的时候,又传入了一个SimpleLayout的实例;关于Layout下面再介绍,现在只需要关注Appender的继承特性。接下来,又创建了一个的子Logger;并且使用这个Logger输出了两条日志信息。运行测试,输出:
INFO - log2 info
DEBUG - log2 debug
    请注意这个输出,很明显已经和之前的输出信息的格式完全不一样了,这里的输出格式就是由我们在cd这个Logger上面设置的ConsoleAppender+SimpleLayout所规定的。从这个例子中,我们可以看到,我们改变了cd这个Logger的Appender;他下面的子Logger自然就继承了这个Appender,输出了另外一种格式的信息。从这段代码中,我们能看出Logger的继承性,假如我们把代码修改为以下这样:
@Test
public void testLogAppender1(){
    ();
    Logger log1=("cd");
    ();
    (new ConsoleAppender(new SimpleLayout()));
    Logger log2=("");
    ("log2 info");
    ("log2 debug");
}

    在这段代码中,我们仅仅只是添加了BasicConfigurator来完成一个基本的配置。我们先来分析下这段代码。首先我们完成了基本的配置,从前面的测试代码中,我们可以知道,这条代码为rootLogger设置了一个DEBUG的Level;另外,现在我们知道了,这条代码肯定还为rootLogger添加了一个ConsoleAppender。然后我们创建了名字为cd的Logger,并且另外添加了一个Appender;接着又创建了名字为的Logger,最后使用这个Logger输出了两条日志信息。根据前面的Level的表现,我们猜想,当使用这个Logger做日志的时候,因为这个Logger本身是没有添加任何Appender,所以他会向上查询任何一个添加了Appender的父Logger,即找到了cd这个Logger,最后使用cd这个Logger完成日志,那么我们预计的结果是这段代码和上一段代码输出相同。
    我们来运行一下这段代码,输出:
INFO - log2 info
0 [main] INFO   - log2 info
DEBUG - log2 debug
0 [main] DEBUG   - log2 debug
    和我们预测的结果不一样。log2 info和log2 debug分别被输出了两次。我们观察结果,两条日志的输出都是先有一条ConsoleAppender+SimpleLayout的方式输出的然后紧跟一条使用BasicConfigurator的输出方式。那我们就能大胆的猜测了,Logger上的Appender不光能继承其父Logger上的Appender,更重要的是,他不光只继承一个,而是只要是其父Logger,其上指定的Appender都会追加到这个子Logger之上。所以,这个例子中,这个Logger不光继承了cd这个Logger上的Appender,还得到了rootLogger上的Appender;所以输出了这样的结果。在Log4J中,这个特性叫做Appender的追加性。默认情况下,所有的Logger都自动具有追加性,通过一个表来说明:
Logger addAppender 起作用的Appender
root A1 A1
cd A2 A2,A1
null A2,A1
A3 A3,A2,A1
    但是,在某些情况下,这样做反而会引起日志输出的混乱。有些时候,我们并不希望Logger具有追加性。比如在上面这张表中,我们想让只需要继承A2和自己的A3Appender,而不想使用root上面的A1 Appender,又该怎么做呢?
    其实很简单,在Logger上,都有一个setAdditivity方法,如果设置setAdditivity为false,则该logger的子类停止追加该logger之上的Appender;如果设置为true,则具有追加性。修改一下上表:
Logger addAppender setAdditivity 起作用的Appender
root A1 true A1
cd A2 false A2
null true A2
A3 true A3,A2
    再来一段代码看看是否如此:
@Test
public void testLogAppender2() throws Exception{
    ();
    Logger log1=("cd");
    (false);
    (new ConsoleAppender(new SimpleLayout()));

    Logger log2=("");
    (new FileAppender(new SimpleLayout(),""));

    Logger log3=("");
    ("log2 info");
}

    先来分析这段代码,在这段代码中有一些新的知识,简单理解即可。首先,我们使用()方法配置了rootLogger;接着定义了名称为cd的Logger;并为其添加了一个ConsoleAppender,但是这里,我们这里设置了additivity为false,即cd和cd之后的logger都不会再添加rootLogger的Appender了。接下来,我们创建了这个Logger,并且为这个Logger指定了一个FileAppender。FileAppender很简单,除了同样要指定一个Layout,这个在后面介绍,第二个参数还需要指定输出日志的名称;最后,我们创建了,并使用这个Logger输出日志。按照上面的表所展示的规律,因为没有指定任何的Appender,所以向上查询。找到,得到其上的FileAppender,因为没有设置additivity,默认为true,继续向上查找,会得到cd的ConsoleAppender;但是因为cd设置了additivity为false,所以不再向上查询,最后,会向FileAppender和ConsoleAppender输出日志。
运行测试,结果:
INFO - log2 info
    并且在应用下增加一个文件,内容为INFO - log2 info。符合我们的预期。
    在Log4J中,一个Logger可以添加多个Appender,不管是通过继承的方式还是通过调用方法添加。只要添加到了某一个Logger之上,在这个Logger之上的任何一个可以被输出的日志都会分别输出到所有的Appender之上。