• log4j源码阅读(一)之Logger


    概述

    log4j是一款非常方便而且强大的开源日志项目,在经过简单的配置后,可以达到相当不错的效果。

    头脑一热决定对log4j的源码进行一定的阅读,其初衷是希望通过源码的阅读,提高写代码的能力。

    log4j其核心概念可分为:

    logger 日志接收器,即程序员在自己的代码中使用如logger.error(...)的形式记录日志。

    append 日志写出器,将logger接收到的日志信息写入到各种设备,如文件,控制台。

    layout 日志格式化器,将输入的日志先进行格式化,再输出。

    log4j将日志分为了几个级别,由低到高分别为:DEBUG < INFO < WARN < ERROR < FATAL

    若低级别的日志能输出,则比之级别高的日志也能输出。

    UML

    第一次画UML,多少有点紧张...

    主要是希望通过UML图能够反映出logger的组织结构,及logger与其他组件的关系,因此很多因素被忽

    略了。之后的说明都将对照着这个图来。

    Logger

    Logger继承自Category,而在Category有这样的说明

    This class has been deprecated and
    replaced by the {@link Logger} <em>subclass</em></b></font>. It
    will be kept around to preserve backward compatibility until mid
    2003.

    name:作为自己的标识,在工厂方法中通过name来new出Logger实例。

    level:每个Logger都有一个Level属性,在输出日志时,将通过方法getEffectiveLevel()获取到本身

    有效的Level,以确定是否可以输出该条日志。稍后将详细说明级别判断流程。

    parent:每个Logger都有个父结点,父子关系将在LoggerRepository中生成。正因为有了父子关系

    所以在getEffectiveLevel方法中,实际上是向父结点方向遍历,找到第一个不为空的Level。也就是

    说,若不明确指定当前结点的level,则使用父结点的level,在之后LoggerRepository的介绍时,会

    知道,有一个公共的父结点RootLogger。

     1   /**
     2      Starting from this category, search the category hierarchy for a
     3      non-null level and return it. Otherwise, return the level of the
     4      root category.
     5 
     6      <p>The Category class is designed so that this method executes as
     7      quickly as possible.
     8    */
     9   public
    10   Level getEffectiveLevel() {
    11     for(Category c = this; c != null; c=c.parent) {
    12       if(c.level != null) {
    13         return c.level;
    14     }
    15     }
    16     return null; // If reached will cause an NullPointerException.
    17   }
    View Code

    aai:每个Logger可关联多个Appender,接收的日志被依次输出到每一个Appender。Logger将对Appender

    的管理代理到了AppenderAttachableImpl,例如addAppender操作实际上是交给aii处理的。

      /**
         Add <code>newAppender</code> to the list of appenders of this
         Category instance.
    
         <p>If <code>newAppender</code> is already in the list of
         appenders, then it won't be added again.
      */
      synchronized
      public
      void addAppender(Appender newAppender) {
        if(aai == null) {
          aai = new AppenderAttachableImpl();
        }
        aai.addAppender(newAppender);
        repository.fireAddAppenderEvent(this, newAppender);
      }
    View Code

    而在AppenderAttachableImpl中则仅将Appender添加到Vector

      /**
         Attach an appender. If the appender is already in the list in
         won't be added again.
      */
      public
      void addAppender(Appender newAppender) {
        // Null values for newAppender parameter are strictly forbidden.
        if(newAppender == null) {
            return;
        }
        
        if(appenderList == null) {
          appenderList = new Vector(1);
        }
        if(!appenderList.contains(newAppender)) {
            appenderList.addElement(newAppender);
        }
      }
    View Code

    debug:类似的还有info,error等,都是用户调用记录日志方法。在判断级别之后,将日志转递给Appender输出

    将日志转化为LoggingEvent后,调用callAppenders向父结点方向遍历,每个一结点都调用AppenderAttachableImpl

    的方法输出日志。实际上,在AppenderAttachableImpl中,将遍历当前结点所关联的所有Appender,依次输出。

      public
      void debug(Object message) {
        if(repository.isDisabled(Level.DEBUG_INT)) {
            return;
        }
        if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
          forcedLog(FQCN, Level.DEBUG, message, null);
        }
      }
    View Code
      /**
         This method creates a new logging event and logs the event
         without further checks.  */
      protected
      void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
        callAppenders(new LoggingEvent(fqcn, this, level, message, t));
      }
    View Code
      public
      void callAppenders(LoggingEvent event) {
        int writes = 0;
    
        for(Category c = this; c != null; c=c.parent) {
          // Protected against simultaneous call to addAppender, removeAppender,...
          synchronized(c) {
        if(c.aai != null) {
          writes += c.aai.appendLoopOnAppenders(event);
        }
        if(!c.additive) {
          break;
        }
          }
        }
    
        if(writes == 0) {
          repository.emitNoAppenderWarning(this);
        }
      }
    View Code

    LoggerRepository

    LoggerRepository是对logger进行管理的仓库,提供工厂方法getLogger(name)来创建新的logger实例。并将所有创建

    的实例,在逻辑上组织成树结构(logger的parent字段)。每个实例都有一个父结点存在。而LoggerRepository本身包

    含一个RootLogger成员,是所有logger的共享祖先结点。也正是因为这个树结构的存在,在形如getEffectiveLevel等操

    作中,都会向父结点方向遍历。所有子结点在没有特别配置过时,都使用父结点的属性(级别,输出器)。

    LoggerRepository本身也带有level属性,在记录日志时,首先判断的是级别是否超过仓库的级别。该属性默认为ALL。

      public
      void debug(Object message) {
        if(repository.isDisabled(Level.DEBUG_INT)) {
            return;
        }
    ...
    View Code
      public
      boolean isDisabled(int level) {
        return thresholdInt > level;
      }
    View Code

    Appender

    logger将接收到的日志转化成LoggingEvent,通过callAppender方法,交由代理AppenderAttachableImpl完成输出。

    AppenderAttachableImpl在遍历所有关联的Appender后,依次调用其doAppender方法进行输出。而每一个Appender

    也是有优先级概念的,其他和logger的level相同。因此在Appender执行输出的时候,也是要优先判断级别是否符合当

    前Appender的设置。另外,Appender提供一些过滤器,可对输出进行进一步的控制(应该是个简单的职责链模式)。

     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);    
      }
    View Code

    当然输出之前,还需要通过Layout.format(...)格式化后再输出。

    LogMagager

    这是一个用户接口,是上面UML图中没有体现出来的内容。其主要是提供一些工厂,和一些默认的配置。如默认的仓库。

    同时还提供一些工厂方法,如getLogger,内容只是将转交给其他模块实现。

      static {
        // By default we use a DefaultRepositorySelector which always returns 'h'.
        Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);
    }
    View Code

    因此,用户可像通过LogManager使用logger,TestMain.class将最终转化成name。

    public static Logger LOG = LogManager.getLogger(TestMain.class);
    View Code

    小结

    阅读源码时,仅对大框架做了一些了解,如Appender,layout的实现细节,都简单略过了。log4j虽然强大,但终归只是记录

    日志,源码不复杂,有兴趣的可以自己阅读一次。对于我来说,这也仅是一个源码阅读的开始吧。希望以后能学习到更多

    优秀的设计。

  • 相关阅读:
    nodejs ---day01
    面向对象
    面向对象
    面向对象
    webpack
    webpack
    webpack
    模块化 (ESM) --- day02
    模块化 --- day01
    轮播图(淡入淡出切换)
  • 原文地址:https://www.cnblogs.com/fullstack/p/3911187.html
Copyright © 2020-2023  润新知