Appender of Log4j [1]

本文档使用的Log4j版本为1.2.17

基本感官

Log4j Appender主要用来控制日志信息的输出目的地,这些目的地具体可以是标准输出(ConsoleAppender)、文件系统(FileAppender、DailyRollingFileAppender)、邮件(SMTPAppender)、操作系统日志守护进程(SyslogAppender)、Telnet监听(TelnetAppender)、远程套接字(SocketAppender)、数据库(JDBCAppender)等。除了上面log4j默认支持的Appender实现,用户还可以根据具体需要重写或添加自定义的日志输出实现。

Appender拥有对Layout、ErrorHandler、Filter等log4j核心模块的对象引用,相关实现调用流程一般为:

  1. 根据用户配置文件将Appender注册到Logger中。
  2. 在应用触发写日志时,log4j会遍历注册到Logger上的所有Appender,调用Appender的doAppend()方法完成LoggingEvent到Appender的序列化写入。
  3. 在doAppender中,具体调用了Filter对LoggingEvent过滤,在一个Appender上关联的Filter组织为一个单向链表结构。循环处理每一个Filter,过滤结果可能有DENY(拒绝此LoggingEvent输出,结束循环遍历)、NEUTRAL(对此LoggingEvent保持中立,continue循环并尝试下一个Filter)、ACCEPT(接受此LoggingEvent,结束Filter遍历循环)三种情况。
  4. AppenderSkeleton是Appender下的一个抽象类,实现了Appender的大多数接口,如果具体的日志实现Appender继承自AppenderSkeleton,则在doAppend开始时还会检查日志级别是否超过日志输出阈值,只有符合阈值限制的LoggingEvent才会输出。
  5. 在doAppend函数中,具体还会调用Layout的format函数,将LoggingEvent转换到日志字符串序列化表示形式。如果Layout的ignoresThrowable为true,则由Appender具体调用LoggingEvent.getThrowableStrRep()序列化输出异常信息。

注意,log4j本身的异常和错误(如流操作异常、配置异常)不会阻塞应用,即log4j本身不会抛出异常到应用中。不过在并发环境中,由于Appender的相关流写出操作需要同步加锁,在序列化输出LoggingEvent时,可能block住应用线程,导致应用效率问题。

体系结构

Appender是对日志输出目的地的抽象:

// 增加一个Filter,一般实现中多个Filter以单向链表维护,每次添加在链表尾部
void  addFilter(Filter newFilter);
// 获取Filter,为Filter链表的头部head,可通过head.getNext()获取后续Filter 
public  Filter getFilter(); 
// 清空Appender关联的所有Filter
public  void clearFilters(); 
// 关闭Appender并释放相应的资源,如:网络连接、文件句柄等
public  void close(); 
// 具体将一次日志LoggingEvent输出至Appender,不同日志目的有不同的数据流写出方式
public  void doAppend(LoggingEvent event); 
// 获取Appender的名字
public  String getName(); 
// 设置Appender关联的ErrorHandler,ErrorHandler用于处理Appender中出现的问题和错误
public  void setErrorHandler(ErrorHandler errorHandler); 
// 获取Appender关联的ErrorHandler,每个Appender都关联一个ErrorHandler
public  ErrorHandler getErrorHandler(); 
// 设置Appender关联的Layout,具体日志格式化时,使用了Layout的format函数
public  void setLayout(Layout layout); 
public  Layout getLayout(); // 获取Appender关联的Layout
// 设置Appender的名字
public  void setName(String name); 
// Configurators调用此函数判断一个Appender是否需要一个Layout,如果为true,在生成Appender实例对象时,会根据配置文件为Appender关联一个Layout
public  boolean requiresLayout(); 

appender

AppenderSkeleton抽象类为Appender提供了基础实现,具体不同的写入目标可以继承AppenderSkeleton定制具体的写出目标。Appender/AppenderSkeleton继承体系结构如下图所示:

appender2

主要是重写public void append(LoggingEvent event)方法,AppenderSkeleton继承体系的子类在doAppend调用时具体会调用子类的append方法完成实际日志message的写出。本质上AppenderSkeleton实现了比较通用的Appender功能(过滤器、日志级别过滤等),具体写入日志实现细节由子类实现。详细见AppenderSkeleton小节。

0.AppenderSkeleton

AppenderSkeleton抽象类实现了Appender接口,提供了Appender相关的通用功能,如日志级别过滤、Filter过滤和处理等,log4j自带的具体Appender实现大多继承此抽象类。

同时,AppenderSkeleton也实现了OptionHandler接口,OptionHandler 仅包含一个方法 activateOptions()。对实现OptionHandler接口的模块的属性调用 setter 方法后,一个配置器类初始化实例对象时,会调用此模块的activateOptions实现以激活配置 。有些属性彼此依赖,因此它们在全部加载完成之前是无法激活的,这个方法提供了在模块变为激活和就绪之前用来执行任何必要任务的机制。比如某模块有字符串属性fileName 属性,即log4j用户配置日志文件名,由 activateOptions具体完成文件的打开、获取文件写出流等,具体见 FileAppender中对文件名和文件IO打开的操作。

AppenderSkeleton提供了下面的几个成员属性,并override实现了Appender接口中的几个成员setXXX和getXXX接口。

protected Layout layout;
protected String name;
protected Priority threshold;
protected ErrorHandler errorHandler;

对于Filter,AppenderSkeleton使用headFilter指向Filter链表的头部、使用tailFilter指向Filter链表的尾部。整个Filter链组织为一个单线链表。根据headFilter、tailFilter实现了addFilter 、getFilter、clearFilters接口。对应AppenderSkeleton成员属性为:

protected Filter headFilter;
protected Filter tailFilter;

AppenderSkeleton对于doAppend接口(Appender中的接口)的实现逻辑具体为:

  1. 判断对应的输出流是否已经关闭,如果关闭则打印响应的错误信息。
  2. 判断当前LoggingEvent对应的日志级别,是否超出Appender配置的阈值,只有不小于阈值的LoggingEvent才可能输出。
  3. 处理Filter过滤链,过滤结果可能有DENY(拒绝此LoggingEvent输出,结束循环遍历)、NEUTRAL(对此LoggingEvent保持中立,continue循环并尝试下一个Filter)、ACCEPT(接受此LoggingEvent,结束Filter遍历循环)三种情况。
  4. 在完成上面3步后,调用AppenderSkeleton增加的protected函数append,完成具体的日志写出。AppenderSkeleton的子类需要实现append方法,完成具体的日志LoggingEvent写出。
  public synchronized void doAppend(LoggingEvent event) {
    if(closed) {
      LogLog.error("Attempted to append to closed appender named ["+name+"].");
      return;
    }
    
    if(!isAsSevereAsThreshold(event.getLevel())) {
      return;
    }

    Filter f = this.headFilter;
    
    FILTER_LOOP:
    while(f != null) {
      switch(f.decide(event)) {
      case Filter.DENY: return;
      case Filter.ACCEPT: break FILTER_LOOP;
      case Filter.NEUTRAL: f = f.getNext();
      }
    }
    this.append(event);    
  }

说明,注意方法调用链,doAppend-> append 在具体子类实现时会有进一步的细化doAppend-> append -> subAppend 即每一层提供一个通用功能,将可变部分抽出来,可以由子类定制化。这里实际使用了设计模式中的模版方法模式,即完成一个算法框架,指定了算法的步骤和顺序,由子类完成某个具体步骤的逻辑。

abstract protected void append(LoggingEvent event);

说明,AppenderSkeleton在Appender doAppend接口基础上增加了Appender关闭、日志阈值、过滤器Filter判断功能,AppenderSkeleton的子类override重写append函数处理具体日志写出;doAppend函数是同步方法,有synchronized关键字,注意加锁相关对性能的影响。

下面介绍AppenderSkeleton的具体实现类们~

标签: none
添加新评论