`
wubo.wb
  • 浏览: 28657 次
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

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上打印日志。
分享到:
评论

相关推荐

    Log4j学习笔记和一套完整定义实例

    **Log4j学习笔记** Log4j是一款广泛应用于Java编程中的日志记录框架,由Apache软件基金会开发。它提供了一套灵活的日志记录系统,帮助开发者在开发过程中追踪应用程序的行为,便于调试、性能分析和问题排查。Log4j...

    log4j多文件输出打印

    本话题将深入探讨如何使用Log4j实现多文件输出打印,以及自定义日志的配置。 首先,我们要理解Log4j的基本工作原理。Log4j的核心组件包括Logger、Appender和Layout。Logger负责生成日志事件,Appender则负责接收...

    日志管理Log4j学习笔记

    ### Log4j学习笔记知识点详解 #### 一、Log4j概述 - **定义与功能**:Log4j是Apache组织推出的一个开源日志框架,主要用于控制和管理应用程序中的日志记录过程。它允许开发者非常灵活地配置日志输出的目的地(如...

    log4j删除前N天日志

    Log4j是一个广泛使用的开源日志记录框架,提供灵活的日志配置和强大的日志输出功能。本话题将深入探讨如何使用Log4j中的DailyRollingFileAppender配置来自动删除前N天的日志,以便保持日志文件的大小和数量在可控...

    log4J学习笔记

    ### log4J学习笔记 #### 一、Apache Commons Logging与log4j及JDK Logger的关系 **Apache Commons Logging** 的设计初衷是为了提供一个简洁统一的日志接口,它并不直接实现日志功能,而是通过桥接的方式支持多种...

    log4j 学习笔记

    "log4j 学习笔记" log4j 是一个流行的 Java 日志记录工具, logger 是记录日志信息的基本单元。在 log4j 中, logger 的级别越低,记录的日志信息越详细。 Logger 的级别从低到高依次是:ALL 。 log4j 的日志记录...

    Log4j 把不同包的日志打印到不同位置

    这篇博客"Log4j 把不同包的日志打印到不同位置"可能详细解释了如何利用Log4j配置来实现特定包的日志定向输出,以便于管理和分析来自不同模块的日志信息。 在Java应用中,我们常常会遇到多个模块或者不同包的类同时...

    log4j按功能保存日志

    Log4j是一款广泛使用的Java日志记录框架,它允许开发者按照功能模块或特定需求记录应用程序运行过程中的事件信息。在“log4j按功能保存日志”的场景中,我们通常会利用Log4j的配置灵活性,将不同功能的日志分别写入...

    log4j2异步多线程打印

    在Log4j2中,异步日志打印是一种提高日志处理效率的重要特性。传统的日志系统在处理大量日志时可能会成为系统性能瓶颈,因为它们通常是同步的,这意味着每个日志事件都会阻塞应用程序的执行直到日志被写入。Log4j2...

    log4j 日志打印

    《深入理解Log4j日志打印》 在Java开发领域,日志打印是不可或缺的一环,它对于系统调试、性能监控、故障排查等都起着至关重要的作用。Log4j作为Java中最常用的日志框架之一,深受广大开发者喜爱。本文将深入探讨...

    Log4j的学习笔记

    **Log4j学习笔记** Log4j是一款广泛应用于Java编程中的日志记录工具,由Apache软件基金会开发。它提供了一种灵活且强大的机制来记录应用的运行情况,这对于调试、性能分析、问题追踪以及系统监控至关重要。这篇学习...

    老生常谈Log4j和Log4j2的区别(推荐)

    下面我们将从配置文件类型、核心JAR包、文件渲染和Log调用四个方面来比较Log4j和Log4j2的区别。 配置文件类型 Log4j通过一个.properties文件作为主配置文件,而Log4j2则弃用了这种方式,采用的是.xml、.json或者....

    mybatis,log4j打印日志到后台和文件

    Log4j 是一个广泛使用的日志记录工具,能够帮助开发者跟踪应用程序运行过程中的信息、警告、错误等事件。本文将深入探讨如何在 MyBatis 中配置 Log4j,实现日志同时输出到后台控制台和文件。 1. **日志框架集成** ...

    log4j2.xml记录日志到到数据库

    Log4j2是一款广泛使用的日志框架,它提供了灵活且高效的日志记录功能。本示例将详细介绍如何配置并使用Log4j2将日志信息记录到MySQL数据库中。 首先,我们要理解Log4j2的核心概念。Log4j2主要包括以下几个组件: 1...

    log4j 学习 笔记

    ### Log4j 学习笔记知识点详解 #### 一、Log4j 概述与架构 **Log4j** 是 **Apache** 组织提供的一个开源日志框架,旨在简化应用开发中的日志记录工作。它能够灵活地记录程序运行时的信息,并允许用户自定义日志的...

    java关于log4j打印日志demo

    通过这个`java关于log4j打印日志demo`,我们可以学习到如何配置和使用Log4j,理解其工作原理,以及如何在实际项目中有效地利用日志系统。实践这个示例将帮助你更好地掌握Java日志记录的最佳实践。

    log4j日志配置以及配置文件详解

    **日志系统在软件开发中扮演着至关重要的角色,它能帮助开发者跟踪程序运行...同时,不断学习和探索log4j的高级特性,如异步日志、MDC(Mapped Diagnostic Context)等,可以进一步提升日志系统的实用性和可维护性。

    log4j自定义日志文件名及日志输出格式

    总之,Log4j的强大之处在于它的灵活性和可扩展性,允许开发者根据具体需求进行深度定制。无论是自定义日志文件名还是输出格式,只要理解了其核心机制,就能轻松应对各种挑战。在项目中使用自定义的日志处理方式,...

    Log4J学习笔记

    ### Log4J学习笔记 #### 一、Log4J配置文件的学习 在深入理解Log4J之前,我们先从配置文件入手。Log4J支持多种配置文件格式,其中包括XML和Java特性文件(键=值)。这里我们将重点介绍如何使用Java特性文件作为...

    log4j使用笔记

    #### 四、log4j 配置详解 1. **配置文件**: - `log4j.properties` 或 `log4j.xml` 文件用于定义 log4j 的配置规则。 - 配置文件通常放置在项目的根目录或者类路径下。 - 如果未指定配置文件的位置,则 log4j 会...

Global site tag (gtag.js) - Google Analytics