首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

log4j学习札记之打印日志

2012-11-15 
log4j学习笔记之打印日志log4j日志级别共有7种:分别是ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF。看Priority类

log4j学习笔记之打印日志
log4j日志级别共有7种:分别是ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF。看Priority类中的定义

public final static int OFF_INT = Integer.MAX_VALUE;public final static int FATAL_INT = 50000;public final static int ERROR_INT = 40000;public final static int WARN_INT  = 30000;public final static int INFO_INT  = 20000;public final static int DEBUG_INT = 10000;public final static int ALL_INT = Integer.MIN_VALUE;

显然是ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF

在log4j中建议使用DEBUG,INFO,WARN,ERROR这四种级别。

在Category类中有很多打印日志的方法
public class Category implements AppenderAttachable {    public void debug(Object message) {}    public void info(Object message) {}    public void warn(Object message) {}    public void error(Object message) {}}

由于org.apache.log4j.Logger继承自Category,所以只要我们获取到一个logger,然后在这个logger上调用以上方法即可打印日志,在Logger类中有很多获取logger的方法,下面以debug方法为例分析一下打印日志的过程。

在完成log4j初始化后,调用logger.debug(""),以下是Category中的方法
  public void debug(Object message) {    if(repository.isDisabled(Level.DEBUG_INT))      return;    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {      forcedLog(FQCN, Level.DEBUG, message, null);    }  }

1、首先判断debug这种级别的日志是否可以显示,Threshold是个全局的过滤器,它将把低于所设置的level的信息过滤不显示出来。
  public boolean isDisabled(int level) {    return thresholdInt > level;  }

2、获取有效Level,调用getEffectiveLevel()方法遍历Logger 的等级结构,找出本Logger 的等级。并和本层次的Log等级比较,判断本Logger能否打印这个层次的Log。如果可以打印就调用forcedLog()方法。forcedLog()方法通过传入得参数生成一个LoggingEvent实例,然后调用callAppenders()方法。
  public Level getEffectiveLevel() {    for(Category c = this; c != null; c=c.parent) {      if(c.level != null)return c.level;    }    return null; // If reached will cause an NullPointerException.  }

  protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {      callAppenders(new LoggingEvent(fqcn, this, level, message, t));  }

  public void callAppenders(LoggingEvent event) {    int writes = 0;    for(Category c = this; c != null; c=c.parent) {      synchronized(c) {if(c.aai != null) {  writes += c.aai.appendLoopOnAppenders(event);}if(!c.additive) {  break;}      }    }    if(writes == 0) {      repository.emitNoAppenderWarning(this);    }  }

  在Logger的继承结构上,每层都有可能有appender,callAppenders(LoggingEvent)方法通过遍历logger继承树,如果本Logger关闭了继承开关,就直接退出循环,否则依次遍历所有祖先的Appender。最后判断写Log的次数,如果等于0就打印没有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;  }

  每个Logger可以有多个Appender,先遍历本Logger中所有的Appender,并调用相应的doAppend()方法。
  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    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);      }

上面是AppenderSkeleton中的方法,AppenderSkeleton是实例Appender接口所有类的父类。append(event)在WriterAppender中有实现
  public void append(LoggingEvent event) {    if(!checkEntryConditions()) {      return;    }    subAppend(event);   }

     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();    }  }

  layout.format(event)将ConvisionPattern中的格式化字符串转换为真正需要打印的信息。下面看看PatternLayout中的format方法
    public String format(LoggingEvent event) {    if(sbuf.capacity() > MAX_CAPACITY) {      sbuf = new StringBuffer(BUF_SIZE);    } else {      sbuf.setLength(0);    }    PatternConverter c = head;    while(c != null) {      c.format(sbuf, event);      c = c.next;    }    return sbuf.toString();  }

在初始化中提到ConvisionPattern被解析为一个链表,这里就是遍历这个链表,并作转换,将最终需要打印的信息存放到sbuf中。
分析一下 c.format(sbuf, event)这个方法
 public void format(StringBuffer sbuf, LoggingEvent e) {    String s = convert(e);    if(s == null) {      if(0 < min)spacePad(sbuf, min);      return;    }    int len = s.length();    if(len > max)      sbuf.append(s.substring(len-max));    else if(len < min) {      if(leftAlign) {sbuf.append(s);spacePad(sbuf, min-len);      }      else {spacePad(sbuf, min-len);sbuf.append(s);      }    }    else      sbuf.append(s);  }

该方法的重点是第一句:String s = convert(e),来看转换过程,PatternConverter的子类有很多,挑选BasicPatternConverter来作分析
    public  String convert(LoggingEvent event) {      switch(type) {      case RELATIVE_TIME_CONVERTER:return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));      case THREAD_CONVERTER:return event.getThreadName();      case LEVEL_CONVERTER:return event.getLevel().toString();      case NDC_CONVERTER:return event.getNDC();      case MESSAGE_CONVERTER: {return event.getRenderedMessage();      }      default: return null;      }    }  }

  以上方法中使用了switch(type),在log4j初始化时,ConvisionPattern类型的链表中的每一个节点并不是存放的%p,%c这种字符串,而是将它们转换成了一系列整型常量。
  private static final int LITERAL_STATE = 0;  private static final int CONVERTER_STATE = 1;  private static final int DOT_STATE = 3;  private static final int MIN_STATE = 4;  private static final int MAX_STATE = 5;  static final int FULL_LOCATION_CONVERTER = 1000;  static final int METHOD_LOCATION_CONVERTER = 1001;  static final int CLASS_LOCATION_CONVERTER = 1002;  static final int LINE_LOCATION_CONVERTER = 1003;  static final int FILE_LOCATION_CONVERTER = 1004;  static final int RELATIVE_TIME_CONVERTER = 2000;  static final int THREAD_CONVERTER = 2001;  static final int LEVEL_CONVERTER = 2002;  static final int NDC_CONVERTER = 2003;  static final int MESSAGE_CONVERTER = 2004;

在遍历链表时,根据这些常量的取值,采取不同的替换方案。例如:RELATIVE_TIME_CONVERTER,会在sbuf中增加一个时间值;LEVEL_CONVERTER在sbuf中记录日志级别等。

总结:日志打印,首先判断日志级别,是否可以打印该种级别的日志,然后遍历logger继承树,对每层的n个appender也进行遍历,最后根据初始化时生成的链表构建打印信息进行转换,生成最终需要打印的信息,在每个appender上打印日志。

热点排行