Appender of Log4j [2]

下面介绍AppenderSkeleton的具体实现类们~
注意,AppenderSkeleton的实现类们会继承AppenderSkeleton可配置的一些属性,介绍具体子类时就不一一体现描述这些AppenderSkeleton父类的属性了。

1.NullAppender

NullAppender重载了doAppend、append方法,函数体都为空,NullAppender并不会将任何LoggingEvent输出,功效类似于linux中的/dev/null,应用于需要使用日志框架但又对到达NullAppender的日志内容不关心的情景。

/**
* Does not do anything.
* */
public void doAppend(LoggingEvent event) {
}

/**
* Does not do anything.
* */
protected void append(LoggingEvent event) {
}

demo java code:

@Test
public void test() {
    Logger logger = Logger.getLogger("demo.ab");
    NDC.push("ndc message");
    MDC.put("traceId", "uuid");
    logger.info("info:123");
    MDC.clear();
    logger.warn("warn:abc");
    logger.error("error:xyz");
    logger.info("exception", new RuntimeException("run time exception"));
}

demo log4j config:

log4j.rootLogger=INFO,NullConsole
log4j.appender.NullConsole=org.apache.log4j.varia.NullAppender
log4j.appender.NullConsole.layout=org.apache.log4j.PatternLayout
log4j.appender.NullConsole.layout.ConversionPattern=%d %-5p [%t] [%F:%L] [%X{traceId}] - %m%n

本地文件流相关的Appender有:

  • WriterAppender
  • ConsoleAppender
  • FileAppender
  • DailyRollingFileAppender
  • RollingFileAppender
  • ExternallyRolledFileAppender
  • 诸如此类的

2.WriterAppender

WriterAppender继承自AppenderSkeleton,具体实现将LoggingEvent输出到java.io.Writer或java.io.OutputStream(输出字符流、字节流中)。这里的流需要提前打开,否则WriterAppender会写出失败,在WriterAppender消亡时,log4j会将流关闭。WriterAppender的各个子类实现,具体即是控制不同的流打开模式,如ConsoleAppender、FileAppender等。

WriterAppender有成员属性encoding 表示流的编码,不设置时使用系统默认编码。WriterAppender有成员属性immediateFlush以及成员函数shouldFlush判断每次LoggingEvent的输出是否都执行流刷出flush操作,子类可重载shouldFlush方法实现自己的是否flush逻辑。

boolean immediateFlush;
protected boolean shouldFlush(final LoggingEvent event)

shouldFlush为true时,每次LoggingEvent输出都尝试进行flush,这会影响IO性能;子类可以缓存日志信息,进行批量flush,此时shouldFlush返回为false,但是使用批量刷新flush情况下,应用崩溃时可能有部分日志信息没有持久化到存储。

WriterAppender对于append函数的重载,doAppend->append->subAppend:

 
  public void append(LoggingEvent event) {

    // Reminder: the nesting of calls is:
    //
    //    doAppend()
    //      - check threshold
    //      - filter
    //      - append();
    //        - checkEntryConditions();
    //        - subAppend();

    if(!checkEntryConditions()) {
      return;
    }
    subAppend(event);
  }

WriterAppender增加了流状态检查逻辑checkEntryConditions,只有在流具备写出条件时才进行日志输出。具体日志写出使用subAppend完成,在subAppend中,使用QuietWriter完成具体LoggingEvent的写出,如果同WriterAppender关联的Layout忽略异常相关,则由WriterAppender自己处理异常输出。注意这里使用QuietWriter,其是Writer的子类,QuietWriter在写出时,对于捕获的异常Exception由ErrorHandler处理,并不会抛出到外部,即:日志流输出异常不会影响应用的正常执行。

  //Actual writing occurs here. Most subclasses of WriterAppender will need to override this method.
  protected void subAppend(LoggingEvent event) {
    this.qw.write(this.layout.format(event));
    if(layout.ignoresThrowable()) {
      String[] s = event.getThrowableStrRep();
      if (s != null) {
        int len = s.length;
        for(int i = 0; i < len; i++) {
          this.qw.write(s[i]);
          this.qw.write(Layout.LINE_SEP);
        }
      }
    }
    if(shouldFlush(event)) {
      this.qw.flush();
    }
  }

WriterAppender的子类一般需要构造不同的QuietWriter获取方式(标准输出、标准错误、文件、轮转新文件等),或者重写subAppend方法,进一步定制流写出的细节,log4j中提供的Appender中,继承自WriterAppender的有ConsoleAppender、FileAppender,分别表示流输出到标准输出、文件等。另外,在流打开时会输出Layout头部layout.getHeader(),在流关闭时输出Layout尾部layout.getFooter()。

demo java code:

@Test
public void testAppender() {
    Logger logger = Logger.getLogger("demo.ab");
    Logger rootLogger = Logger.getRootLogger();
    WriterAppender writerAppender = (WriterAppender) rootLogger
            .getAppender("writerdemo");
    Writer writer = new PrintWriter(System.out);// 流设置为标准输出,其实这时候完全类似于ConsoleAppender的功能
    writerAppender.setWriter(writer);
    NDC.push("ndc message");
    logger.info("info:123");
    logger.warn("warn:123");
    logger.info("exception", new RuntimeException("run time exception"));
}

demo log4j config:

#root级别及日志名
log4j.rootLogger=INFO,writer
#使用WriterAppender
log4j.appender.writer=org.apache.log4j.WriterAppender
#WriterAppender使用即是刷新
log4j.appender.writer.ImmediateFlush=true
#WriterAppender关联的java.io.Writer编码
log4j.appender.writer.Encoding=utf8
#WriterAppender日志级别
log4j.appender.writer.Threshold=debug
#WriterAppender关联的Layout,这里为PatternLayout
log4j.appender.writer.layout=org.apache.log4j.PatternLayout
#PatternLayout对应的转换字符串ConversionPattern
log4j.appender.writer.layout.ConversionPattern=%t %m%n
#WriterAppender的名字,获取一个Logger后,可调用getAppender(String)函数获取关联的指定名字的Appender,如:WriterAppender writerAppender = (WriterAppender) rootLogger.getAppender("writerdemo");
log4j.appender.writer.name=writerdemo

demo 日志输出:

main info:123
main warn:123
main exception
java.lang.RuntimeException: run time exception
at com.luohw.log4j.AppenderTest.testAppender(AppenderTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
... ...
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

3.ConsoleAppender

ConsoleAppender继承自WriterAppender,用于将LoggingEvent输出到标准输出System.out、或者标准错误System.err。ConsoleAppender在WriterAppender的基础上,提供了Target属性,用于指定此Appender关联的输出流。可以配置字符串System.out表示标准输出、字符串System.err表示标准错误。ConsoleAppender的主要逻辑在设置Target上,设置合适的写出流,不需要重写WriterAppender的subAppend方法。

demo java code:

@Test
public void test() {
    Logger logger = Logger.getLogger("demo.ab");
    NDC.push("ndc message");
    MDC.put("traceId", "uuid");
    logger.info("info:123");
    MDC.clear();
    logger.warn("warn:abc");
    logger.error("error:xyz");
    logger.info("exception", new RuntimeException("run time exception"));
}

demo log4j config:

log4j.rootLogger=INFO,writer
log4j.appender.writer=org.apache.log4j.ConsoleAppender
#设置ConsoleAppender关联的输出流为标准输出System.out(另一个可识别的字符串为System.err)
log4j.appender.writer.Target=System.out
log4j.appender.writer.ImmediateFlush=true
log4j.appender.writer.Encoding=utf8
log4j.appender.writer.Threshold=debug
log4j.appender.writer.layout=org.apache.log4j.PatternLayout
log4j.appender.writer.layout.ConversionPattern=%t %m%n
log4j.appender.writer.name=writerdemo

demo 日志输出(标准输出):

main info:123
main warn:123
main exception
java.lang.RuntimeException: run time exception
at com.luohw.log4j.AppenderTest.testAppender(AppenderTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
... ...
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

注意,由于Appender主要控制日志消息的输出目的地,和日志形式关系不明显,后续不再对demo java code、demo 日志输出做过多阐述,但给出demo log4j config的配置示例子。
注意,log4j的各个模块涉及的成员属性时,如果属性有set方法,一般表示此属性可通过log4j.properties进行配置,具体配置属性值为属性的setXXX方法去掉set前缀。示例如上面ConsoleAppender的target属性配置,对于getXXX方法名为setTarget
log4j.appender.writer.Target=System.out

后面继续介绍AppenderSkeleton的具体实现类们~

标签: none
评论列表
  1. God, I feel like I shulod be takin notes! Great work

  2. 也接待博主到我的博客瞧一瞧!
    [url=http://www.bjwlslm.com]腾博会[/url]

  3. 很久没来看博主的文章了,说的很有道理,支持一下!
    ca88亚洲城 http://jwc.hbust.com.cn

  4. hey站长,我想转走你写的这篇文章内容,请问您批准吗?我会保留原文来由的链接以及作者。

    1. Trafalgar

      可以的,请知晓!

添加新评论