Appender of Log4j [6]

其他Appender主要有:

  • LF5Appender
  • NTEventLogAppender
  • RewriteAppender
  • AsyncAppender

15.LF5Appender

LF5Appender是log4j提供的一个图形界面的日志消息查看、检索GUI工具,开发基于swing编程。LF5Appender同样是AppenderSkeleton的子类,requiresLayout返回false,不需要关联Layout。看代码是Contributed by ThoughtWorks Inc.下面截图包下面都是lf5相关的代码,如果有用java做界面的话,可以参考下怎么写swing。

MaxNumberOfRecords是LF5Appender主要配置项,表示LF5Appender最多保留多少日志记录,默认值是5000,LF5Appender不会再内存中hold下那么多日志消息的。
LF5Appender1

15.1demo

demo java code:

// 每10秒循环一次日志输出,避免程序结束swing gui不显示
Logger logger = Logger.getLogger("lf5");
while (true) {
    logger.info("info:123");
    logger.warn("warn:abc");
    logger.error("error:xyz");
    logger.info("exception", new RuntimeException("run time exception"));
    Thread.sleep(10000L);
}

demo log4j config:

log4j.rootLogger=INFO,lf5,R
log4j.appender.lf5=org.apache.log4j.lf5.LF5Appender

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=/tmp/log.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=[slf5s.start]%d{DATE}[slf5s.DATE]%n\
%p[slf5s.PRIORITY]%n%x[slf5s.NDC]%n%t[slf5s.THREAD]%n\
%c[slf5s.CATEGORY]%n%l[slf5s.LOCATION]%n%m[slf5s.MESSAGE]%n%n
log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1

demo 界面效果:

LF5Appender2

LF5Appender也支持查看已有日志文件,不过需要日志文本符合LF5Appender的格式要求,本例子中的RollingFileAppender的ConversionPattern即是LF5Appender的格式要求:

[slf5s.start]%d{DATE}[slf5s.DATE]%n\
%p[slf5s.PRIORITY]%n%x[slf5s.NDC]%n%t[slf5s.THREAD]%n\
%c[slf5s.CATEGORY]%n%l[slf5s.LOCATION]%n%m[slf5s.MESSAGE]%n%n
LF5Appender的一些FAQ:
http://people.apache.org/~carnold/log4j/docs/x/logging-log4j-1.2.10/docs/lf5/trouble.html

16.NTEventLogAppender

NTEventLogAppender用于将日志信息写出到windows操作系统的NT 日志系统,注意此NTEventLogAppender是和平台相关的,只能在Windows操作系统下运行。

需要将NTEventLogAppender.dll以及NTEventLogAppender.amd64.dll、NTEventLogAppender.ia64.dll或NTEventLogAppender.x86.dll等几个动态链接库加入到Windows的PATH环境变量中,否则可能出现java.lang.UnsatisfiedLinkError异常。NTEventLogAppender类初始化时,会根据System.getProperty("os.arch"),并调用System.loadLibrary将相关dll加载进来。支持的操作架构有amd64, ia64, x86。

NTEventLogAppender的append实现如下:

  public void append(LoggingEvent event) {
    StringBuffer sbuf = new StringBuffer();
    sbuf.append(layout.format(event));
    if(layout.ignoresThrowable()) {
      String[] s = event.getThrowableStrRep();
      if (s != null) {
        int len = s.length;
        for(int i = 0; i < len; i++) {
          sbuf.append(s[i]);
        }
      }
    }
    int nt_category = event.getLevel().toInt();
    reportEvent(_handle, sbuf.toString(), nt_category);
  }

和NT日志系统相关的资源打开、写出、关闭都是来自上面几个动态链接库的native原生调用。

native private int registerEventSource(String server, String source);
native private void reportEvent(int handle, String message, int level);
native private void deregisterEventSource(int handle);

17.RewriteAppender

RewriteAppender继承自AppenderSkeleton类,主要目的是对于LoggingEvent中的信息进行修改、过滤等,比如移除密码等敏感信息等。RewriteAppender需要RewritePolicy指定相关的重写策略。

17.1RewritePolicy

RewritePolicy接口的目的即是为RewriteAppender提供重写策略,在RewriteAppender进行日志输出doAppend时,会调用RewritePolicy的rewrite方法,具体可以返回原有LoggingEvent 、创建新的LoggingEvent并返回、返回null等。RewritePolicy 接口代码如下:

public interface RewritePolicy {
    LoggingEvent rewrite(final LoggingEvent source);
}

RewritePolicy接口实现类有:

  • MapRewritePolicy 针对LoggingEvent的message属性是否java.util.Map的实例,进行属性过滤。如果LoggingEvent的getMessage方法返回对象不是java.util.Map的实例则重写策略是完全不变化原有LoggingEvent;如果是java.util.Map的实例,则重写规则是:新创建一个新的LoggingEvent,新LoggingEvent的message是原有getMessage对象的message属性,其余属性放入新的LoggingEvent的MDC中。
  • PropertyRewritePolicy 针对用户自定义属性key/value的重写规则,对于一个LoggingEvent,如果其MDC中不包含用户配置的属性,则新创建一个LoggingEvent,新的LoggingEvent有原来LoggingEvent和用户自定义的属性。用户配置属性格式为:propname1=propvalue1,propname2=propvalue2
  • ReflectionRewritePolicy 针对反射的重写规则,对于LoggingEvent的message属性,如果其是String类型的实例,则原样输出;如果为非String对象,java.beans.Introspector获取方法信息反射调用。对于message名称的方法,其返回值作为新LoggingEvent的message,其他方法返回值作为新LoggingEvent的MDC。

17.2AppenderAttachable

另外,RewriteAppender也实现了AppenderAttachable接口。AppenderAttachable接口定义了Appender的链式管理和调用能力,类似于Filter的链表组织模式。AppenderAttachable包含的接口有:

// 加入一个新的Appender
public void addAppender(Appender newAppender);
// 获取所有的Appender
public Enumeration getAllAppenders();
// 获取指定名字的Appender,Appender有Name的配置属性
public Appender getAppender(String name);
// 判断一个Appender是否在AppenderAttachable中
public boolean isAttached(Appender appender);
// 删除AppenderAttachable下的所有Appender
void removeAllAppenders();
// 删除指定的AppenderAttachable
void removeAppender(Appender appender);
// 删除指定名称的Appender
void removeAppender(String name);

实现了AppenderAttachable接口的子类有:

  • AppenderAttachableImpl
  • AsyncAppender 异步日志输出,使用AppenderAttachableImpl内部成员属性,委托完成AppenderAttachable接口
  • Category(org.apache.log4j.Logger的父类) log4j功能的总入口,使用AppenderAttachableImpl内部成员属性,委托完成AppenderAttachable接口
  • RewriteAppender 本小节具体介绍的Appender,使用AppenderAttachableImpl内部成员属性,委托完成AppenderAttachable接口

AppenderAttachableImpl是AppenderAttachable的一个简单实现,具体使用了Vector存储和管理各个Appender,相关的增删查等实现都依赖Vector集合类的相关功能。AppenderAttachableImpl 在AppenderAttachable基础上增加了appendLoopOnAppenders方法,实现内容是依次遍历Vector中有的Appender,并调用其对应的doAppend方法。此方法具体实现了将一个LoggingEvent写出到多个Appender。

  public int appendLoopOnAppenders(LoggingEvent event) {
    int size = 0;
    Appender appender;
    if(appenderList != null) {
      size = appenderList.size();
      for(int i = 0; i < size; i++) {
        appender = (Appender) appenderList.elementAt(i);
        appender.doAppend(event);
      }
    }    
    return size;
  }
为什么不在AppenderAttachable接口中增加appendLoopOnAppenders呢?

也许因为AppenderAttachable只是提供了多个Appender统一管理、遍历等相关的机制,管理的多个Appender并不一定是用于日志输出的,也可仅仅用于Appender统一存储管理(虽然log4j实现中AppenderAttachable主要目的是将一个LoggingEvent写出到多个Appender)。

17.2RewriteAppender配置demo

RewriteAppender不支持使用.properties配置文件配置,仅支持java编码配置或xml配置文件配置。.properties不能很方便的表示RewriteAppender对象引用和1对多关系。另外,作为一种面向LoggingEvent重写的Appender,RewriteAppender本身不需要关联Layout,即requiresLayout方法返回false。

demo java code:

Logger logger = Logger.getLogger("rewrite");
logger.info("info:123");
logger.warn("warn:abc");
logger.error("error:xyz");
logger.info("exception", new RuntimeException("run time exception"));

demo log4j config:

配置了RewriteAppender以及PropertyRewritePolicy重写规则

<?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>

标准输出:
RewriteAppender1

文件输出:
RewriteAppender2

17.3log4j是怎么解析RewriteAppender的配置的?

在google上搜索了RewriteAppender的相关配置,并没有找到有参考价值的资料(有的也都是log4j 2系列版本的),阅读代码发现,RewriteAppender实现了

UnrecognizedElementHandler

接口,实现了

parseUnrecognizedElement

方法。

log4j的配置类(RewriteAppender只支持XML配置方式)

org.apache.log4j.xml.DOMConfigurator

解析用户配置文件如log4j.xml时,发现未定义的配置参数时,如果对应实体(这里是RewriteAppender)实现了UnrecognizedElementHandler接口,则调用parseUnrecognizedElement方法尝试解析此部分参数。这样log4j提供了一种扩展配置的方式,对于一些log4j.dtd并不提供标准(内置如RewriteAppender或第三方开发)配置的,可以通过UnrecognizedElementHandler机制进行配置。

public boolean parseUnrecognizedElement(final Element element,
    final Properties props) throws Exception {
    // RewriteAppender中添加Appender已经是log4j内置配置支持元素了,具体是
    final String nodeName = element.getNodeName();
    // 如果元素名是rewritePolicy,则尝试配置RewriteAppender的重写规则
    if ("rewritePolicy".equals(nodeName)) {
        Object rewritePolicy = org.apache.log4j.xml.DOMConfigurator.parseElement(
            element, props, RewritePolicy.class);
        if (rewritePolicy != null) {
            // 根据重写规则是否实现OptionHandler,表示是否需要配置激活处理
            if (rewritePolicy instanceof OptionHandler) {
                ((OptionHandler) rewritePolicy).activateOptions();
            }
            this.setRewritePolicy((RewritePolicy) rewritePolicy);
        }
        return true;
    }
    return false;
}

下面RewriteAppender配置可对比代码查看:

<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>

注意:由于log4j发现未定义的配置项,会打印一些WARN消息,如下:

log4j:WARN Continuable parsing error 19 and column 79
log4j:WARN Element type "rewritePolicy" must be declared.
log4j:WARN Continuable parsing error 22 and column 16
log4j:WARN The content of element type "appender" must match "(errorHandler?,param*,rollingPolicy?,triggeringPolicy?,connectionSource?,layout?,filter*,appender-ref*)".

18.AsyncAppender

AsyncAppender继承自AppenderSkeleton,提供了异步写出日志的机制。AsyncAppender会先缓存应用触发产生的LoggingEvent,在缓冲区攒满后,使用一个独立的线程将日志输出到AsyncAppender绑定的各个Appender中。AsyncAppender同样实现了AppenderAttachable接口,AsyncAppender内部持有AppenderAttachableImpl类型的实例,类似RewriteAppender方式实现。AsyncAppender不依赖Layout,即requiresLayout返回false。

注意:类似RewriteAppender的配置方式,RewriteAppender也只能使用XML格式的配置文件(org.apache.log4j.xml.DOMConfigurator解析)进行配置,不支持.properties配置文件配置。

18.1AsyncAppender配置属性

  • LocationInfo 在AsyncAppender日志分发线程中,分发的日志消息是否包含位置信息,默认false
  • BufferSize AsyncAppender的缓冲区大小,即构造的ArrayList的大小
  • Blocking 当RewriteAppender缓冲区满时的行为,如果为true表示当前线程会尝试阻塞等待缓冲区有新的空间空余出来;如果为false,表示丢弃掉此次LoggingEvent,AsyncAppender会记录应用丢弃的最高日志级别的LoggingEvent以及丢弃的日志记录数目

18.2实现细节

AsyncAppender的核心逻辑在append函数中,具体实现了生产者消费者模式中的生产者逻辑,消费者线程由AsyncAppender的内部类Dispatcher实现。append逻辑如下:

  public void append(final LoggingEvent event) {
    // 如果分发消息线程消亡,则使用同步模式分发写出日志
    if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) {
      synchronized (appenders) {
        appenders.appendLoopOnAppenders(event);
      }
      return;
    }
    // 获取日志消息的渲染表示
    event.getNDC();
    event.getThreadName();
    event.getMDCCopy();
    if (locationInfo) {
      event.getLocationInformation();
    }
    event.getRenderedMessage();
    event.getThrowableStrRep();

    // 本质上是生产者消费者模型,这里是生产者代码
    synchronized (buffer) {
      while (true) {
        int previousSize = buffer.size();
        if (previousSize < bufferSize) {
          buffer.add(event);
          // 如果原来缓冲区为空,给消费者发信号有新的LoggingEvent到达,可以考虑分发了
          if (previousSize == 0) {
            buffer.notifyAll();
          }
          break;
        }

        // 此部分逻辑只有在缓冲区满时到达,用于处理缓冲区满时的LoggingEvent分发行为:阻塞等待、或者丢弃
        boolean discard = true;
        if (blocking
                && !Thread.interrupted()
                && Thread.currentThread() != dispatcher) {
          try {
            buffer.wait();
            discard = false;
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
          }
        }

        // 如果是丢弃的化,更新丢弃的LoggingEvent记录信息,主要是日志数量、最高丢弃日志级别
        if (discard) {
          String loggerName = event.getLoggerName();
          DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName);
          if (summary == null) {
            summary = new DiscardSummary(event);
            discardMap.put(loggerName, summary);
          } else {
            summary.add(event);
          }
          break;
        }
      }
    }
  }

AsyncAppender的内部类Dispatcher实现了消息分发线程的逻辑,即消费者。线程run逻辑代码如下:

    public void run() {
      boolean isActive = true;
      // 如果线程接收到InterruptedException ,线程退出
      try {
        // Dispatcher线程轮训直到AsyncAppender被关闭,调用了close方法
        while (isActive) {
          LoggingEvent[] events = null;
          // 对buffer加锁,buffer是AsyncAppender的append方法和此处线程执行体互斥加锁的对象
          synchronized (buffer) {
            int bufferSize = buffer.size();
            isActive = !parent.closed;
            // 如果缓冲区为空,且AsyncAppender未被关闭,则调用wait方法,释放锁并等待唤醒
            while ((bufferSize == 0) && isActive) {
              buffer.wait();
              bufferSize = buffer.size();
              isActive = !parent.closed;
            }
            // 到这里说明缓冲区中确实有LoggingEvent等待分发
            if (bufferSize > 0) {
              events = new LoggingEvent[bufferSize + discardMap.size()];
              buffer.toArray(events);
              // 将丢弃的信息加入到待分发的LoggingEvent数组中
              int index = bufferSize;
              for (
                Iterator iter = discardMap.values().iterator();
                  iter.hasNext();) {
                events[index++] = ((DiscardSummary) iter.next()).createEvent();
              }
              // 清空当前缓冲区和丢弃日志记录
              buffer.clear();
              discardMap.clear();
              // 唤醒等待缓冲区的生产者又可以放LoggingEvent进缓冲区了
              buffer.notifyAll();
            }
          }

          // 如果待本次分发的LoggingEvent数组不空,则循环AsyncAppender关联的所有Appender,进行日志刷出
          if (events != null) {
            for (int i = 0; i < events.length; i++) {
              synchronized (appenders) {
                appenders.appendLoopOnAppenders(events[i]);
              }
            }
          }
        }
      } catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
      }
    }
  }

18.3demo

log4j config

<?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="AsyncAppender" class="org.apache.log4j.AsyncAppender">

       <appender-ref ref="FileAppender" />

       <appender-ref ref="ConsoleAppender" />

       <param name="LocationInfo" value="true" />

       <param name="BufferSize" value="128" />

       <param name="Blocking" value="false" />

   </appender>

   <!--logger的设置-->

    <root>

        <priority value="debug" />

       <appender-ref ref="AsyncAppender" />

    </root>

</log4j:configuration>

appender done~

标签: none
评论列表
  1. hack

    alert("gg");

  2. 你好站长,我想转走你写的这篇文章,叨教您赞同吗?我会保留原文来由的链接跟作者。
    腾博会 http://www.bjwlslm.com

  3. 不错学习了,谢谢分享!

  4. 看起来比较赞 可以让我转载这篇文章内容吗?
    腾博会 http://www.bjwlslm.com

  5. 今天才发现你的博客,连着看了几篇呢
    ca88亚洲城 http://yjsc.hbust.com.cn

添加新评论