`

读logback源码系列文章(四)——记录日志

阅读更多
今天晚上本来想来写一下Logger怎么记录日志,以及Appender组件。不过9点才从丈母娘家回来,又被几个兄弟喊去喝酒,结果回来晚了,所以时间就只够写一篇Logger类的源码分析了。Appender找时间再写

上篇博客介绍了LoggerContext怎么生成Logger,Logger是logback的核心类,也是所有日志框架的核心类。这篇博客详细介绍一下Logger的各字段和方法,重点介绍Logger类是怎样记录日志的

老规矩,首先看图:


Logger类实现了slf4j框架定义的Logger接口,然后这个类和LoggerContext是互相引用的(因为Logger需要依赖LoggerContext的TurboFilter等组件)。并且Logger实现了AppenderAttachable接口,它实现该接口的方式,是持有AppenderAttachableImpl类,然后委托该类来实现AppenderAttachable接口定义的方法,这里用到了代理模式,是一个比较精巧的设计

看到了Logger类的全景图,接下来我们逐一介绍Logger类中的各个字段,最后重点介绍Logger类是怎样记录日志的

首先看看Logger的字段
static int instanceCount = 0;

  /**
   * The name of this logger
   */
  private String name;

  // The assigned levelInt of this logger. Can be null.
  private Level level;

  // The effective levelInt is the assigned levelInt and if null, a levelInt is
  // inherited form a parent.
  private int effectiveLevelInt;

  /**
   * The parent of this category. All categories have at least one ancestor
   * which is the root category.
   */
  private Logger parent;

  /**
   * The children of this logger. A logger may have zero or more children.
   */
  private List<Logger> childrenList;

  /**
   * It is assumed that once the 'aai' variable is set to a non-null value, it
   * will never be reset to null. it is further assumed that only place where
   * the 'aai'ariable is set is within the addAppender method. This method is
   * synchronized on 'this' (Logger) protecting against simultaneous
   * re-configuration of this logger (a very unlikely scenario).
   * 
   * <p>
   * It is further assumed that the AppenderAttachableImpl is responsible for
   * its internal synchronization and thread safety. Thus, we can get away with
   * *not* synchronizing on the 'aai' (check null/ read) because
   * <p>
   * 1) the 'aai' variable is immutable once set to non-null
   * <p>
   * 2) 'aai' is getAndSet only within addAppender which is synchronized
   * <p>
   * 3) all the other methods check whether 'aai' is null
   * <p>
   * 4) AppenderAttachableImpl is thread safe
   */
  private transient AppenderAttachableImpl<ILoggingEvent> aai;
  /**
   * Additivity is set to true by default, that is children inherit the
   * appenders of their ancestors by default. If this variable is set to
   * <code>false</code> then the appenders located in the ancestors of this
   * logger will not be used. However, the children of this logger will inherit
   * its appenders, unless the children have their additivity flag set to
   * <code>false</code> too. See the user manual for more details.
   */
  private boolean additive = true;

  final transient LoggerContext loggerContext;
  // loggerRemoteView cannot be final because it may change as a consequence
  // of changes in LoggerContext
  LoggerRemoteView loggerRemoteView;

1、name是Logger的名称
2、level是该Logger的分配级别,当配置文件中没有配置时,这个分配级别可以为null
3、effectiveLevelInt是该Logger的生效级别,会从父Logger继承得到
4、parent和childrenList是这个Logger的父Logger和子Logger,体现了logback的Logger层次结构
5、aai上面已经说到了,Logger是委托这个类实现AppenderAttachable接口,也是委托这个类来调用Appender组件来实际记录日志,所以这个字段是最关键的
6、additive是这个类的Appender叠加性,具体看我的另一篇博客。该字段也是在配置文件中配置的,默认为true
7、loggerRemoteView也是一个VO对象,作用不是很大

介绍完了字段,可以看到Logger的设计还是相当清晰易懂的, 接下来逐一看看Logger中的方法,getter和setter方法就不废话了
private final boolean isRootLogger() {
    // only the root logger has a null parent
    return parent == null;
  }

这个方法用来判断一个Logger是否是根Logger
Logger getChildByName(final String childName) {
    if (childrenList == null) {
      return null;
    } else {
      int len = this.childrenList.size();
      for (int i = 0; i < len; i++) {
        final Logger childLogger_i = (Logger) childrenList.get(i);
        final String childName_i = childLogger_i.getName();

        if (childName.equals(childName_i)) {
          return childLogger_i;
        }
      }
      // no child found
      return null;
    }
  }

这个方法用来得到子Logger,是在LoggerContext的getLogger()方法里调用的,在上一篇博客里已经介绍过了
public synchronized void setLevel(Level newLevel) {
    if (level == newLevel) {
      // nothing to do;
      return;
    }
    if (newLevel == null && isRootLogger()) {
      throw new IllegalArgumentException(
          "The level of the root logger cannot be set to null");
    }

    level = newLevel;
    if (newLevel == null) {
      effectiveLevelInt = parent.effectiveLevelInt;
    } else {
      effectiveLevelInt = newLevel.levelInt;
    }

    if (childrenList != null) {
      int len = childrenList.size();
      for (int i = 0; i < len; i++) {
        Logger child = (Logger) childrenList.get(i);
        // tell child to handle parent levelInt change
        child.handleParentLevelChange(effectiveLevelInt);
      }
    }
    // inform listeners
    loggerContext.fireOnLevelChange(this, newLevel);
  }

  /**
   * This method is invoked by parent logger to let this logger know that the
   * prent's levelInt changed.
   * 
   * @param newParentLevel
   */
  private synchronized void handleParentLevelChange(int newParentLevelInt) {
    // changes in the parent levelInt affect children only if their levelInt is
    // null
    if (level == null) {
      effectiveLevelInt = newParentLevelInt;

      // propagate the parent levelInt change to this logger's children
      if (childrenList != null) {
        int len = childrenList.size();
        for (int i = 0; i < len; i++) {
          Logger child = (Logger) childrenList.get(i);
          child.handleParentLevelChange(newParentLevelInt);
        }
      }
    }
  }

这2个方法是用来改变Logger的生效级别,并且连带改变子Logger的生效级别
Logger createChildByName(final String childName) {
    int i_index = getSeparatorIndexOf(childName, this.name.length() + 1);
    if (i_index != -1) {
      throw new IllegalArgumentException("For logger [" + this.name
          + "] child name [" + childName
          + " passed as parameter, may not include '.' after index"
          + (this.name.length() + 1));
    }

    if (childrenList == null) {
      childrenList = new ArrayList<Logger>(DEFAULT_CHILD_ARRAY_SIZE);
    }
    Logger childLogger;
    childLogger = new Logger(childName, this, this.loggerContext);
    childrenList.add(childLogger);
    childLogger.effectiveLevelInt = this.effectiveLevelInt;
    return childLogger;
  }

这个方法是用来创建子Logger的,并且会设置Logger的父子关系,也是在LoggerContext的getLogger()方法里调用的

接下来就重点介绍Logger组件怎么记录日志了,slf4j定义了Logger接口记录日志的方法是info()、warn()、debug()等,这些方法只是入口,logback是这样实现这些方法的
public void info(String msg) {
    filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, msg, null, null);
  }

当客户端代码调用Logger.info()时,实际上会进入filterAndLog_0_Or3Plus方法,Logger类中还有很多名字很相似的方法,比如filterAndLog_1、filterAndLog_2。据作者自己说,之所以定义这一系列的方法,是为了提高logback的性能
/**
   * The next methods are not merged into one because of the time we gain by not
   * creating a new Object[] with the params. This reduces the cost of not
   * logging by about 20 nanoseconds.
   */

  private final void filterAndLog_0_Or3Plus(final String localFQCN,
      final Marker marker, final Level level, final String msg,
      final Object[] params, final Throwable t) {

    final FilterReply decision = loggerContext
        .getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg,
            params, t);

    if (decision == FilterReply.NEUTRAL) {
      if (effectiveLevelInt > level.levelInt) {
        return;
      }
    } else if (decision == FilterReply.DENY) {
      return;
    }

    buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
  }

该方法首先要请求TurboFilter来判断是否允许记录这次日志信息。TurboFilter是快速筛选的组件,筛选发生在LoggingEvent创建之前,这种设计也是为了提高性能

如果经过过滤,确定要记录这条日志信息,则进入buildLoggingEventAndAppend方法
private void buildLoggingEventAndAppend(final String localFQCN,
      final Marker marker, final Level level, final String msg,
      final Object[] params, final Throwable t) {
    LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
    le.setMarker(marker);
    callAppenders(le);
  }

在这个方法里,首先创建了LoggingEvent对象,然后调用callAppenders()方法,要求该Logger关联的所有Appenders来记录日志

LoggingEvent对象是承载了日志信息的类,最后输出的日志信息,就来源于这个事件对象
/**
   * Invoke all the appenders of this logger.
   * 
   * @param event
   *          The event to log
   */
  public void callAppenders(ILoggingEvent event) {
    int writes = 0;
    for (Logger l = this; l != null; l = l.parent) {
      writes += l.appendLoopOnAppenders(event);
      if (!l.additive) {
        break;
      }
    }
    // No appenders in hierarchy
    if (writes == 0) {
      loggerContext.noAppenderDefinedWarning(this);
    }
  }

经过前面的Filter过滤、日志级别匹配、创建LoggerEvent对象,终于进入了记录日志的方法。该方法会调用此Logger关联的所有Appender,而且还会调用所有父Logger关联的Appender,直到遇到父Logger的additive属性设置为false为止,这也是为什么如果子Logger和父Logger都关联了同样的Appender,则日志信息会重复记录的原因

继续看下来
private int appendLoopOnAppenders(ILoggingEvent event) {
    if (aai != null) {
      return aai.appendLoopOnAppenders(event);
    } else {
      return 0;
    }
  }

实际上调用的AppenderAttachableImpl的appendLoopOnAppenders()方法
/**
   * Call the <code>doAppend</code> method on all attached appenders.
   */
  public int appendLoopOnAppenders(E e) {
    int size = 0;
    r.lock();
    try {
      for (Appender<E> appender : appenderList) {
        appender.doAppend(e);
        size++;
      }
    } finally {
      r.unlock();
    }
    return size;
  }

到这里,为了记录一条日志信息,长长的调用链终于告一段落了,通过调用Appender的doAppend(LoggingEvent e)方法,委托Appender来最终记录日志(其实Appender记录日志信息也是委托其他的类来完成的, 在后面的博客中再介绍)

但是这里没这么简单,AppenderAttachableImpl类为了处理并发情况,是用了读写锁的
final private ReadWriteLock rwLock = new ReentrantReadWriteLock();
  private final Lock r = rwLock.readLock();
  private final Lock w = rwLock.writeLock();

总结来说,Logger类中定义的字段和方法,是出于以下目的:
1、持有LoggerContext,是为了使用TurboFilter来进行快速过滤
2、定义parent和childList,用于实现父子Logger的树形结构
3、定义createChildByName()、getChildByName()方法,是供LoggerContext创建Logger
4、定义level、effectiveLevelInt,是为了判定日志级别是否足够
5、最后,filterAndLog()、buildLoggingEventAndAppend()、callAppenders()、appendLoopOnAppenders()方法,是Logger类的核心方法,一步步地委托AppenderAttachableImpl类来实际记录日志

下一篇博客,准备介绍Appender组件怎么记录日志
  • 大小: 141.6 KB
分享到:
评论
1 楼 穹劲长空 2016-02-17  
     

相关推荐

    SpringBoot 源码解析——SpringBoot 中的日志基础服务.docx

    在SpringBoot框架中,日志服务是应用程序不可或缺的一部分,它提供了一种记录应用运行时详细信息的机制,包括方法的输入参数、运行时的特殊记录等。SpringBoot为开发者提供了灵活的日志配置,支持多种日志系统,如...

    【MyBatis入门】—— MyBatis日志

    这篇博客“【MyBatis入门】—— MyBatis日志”旨在帮助初学者理解如何在MyBatis中配置和使用日志功能,以便于调试和优化应用程序。 在MyBatis中,日志系统对于跟踪SQL语句和参数,以及检查执行效率至关重要。...

    JAVA日志组件系列(二)logback1.0.13and sli4j1.7.5

    本篇文章将深入探讨的是JAVA日志组件中的一个经典组合——logback 1.0.13 和 SLF4J 1.7.5。这两者都是由Ceki Gülcü开发的,他是Java世界中著名的日志框架Log4j的创始人。SLF4J(Simple Logging Facade for Java)...

    logback的使用和logback.xml详解

    标题"Logback的使用和logback.xml详解"暗示了我们要讨论的是一个日志管理框架——Logback,以及它的配置文件`logback.xml`。Logback是Java社区广泛使用的日志处理系统,由Ceki Gülcü创建,作为Log4j的后继者。它...

    log4j logback slf4j

    本文将深入探讨三个重要的Java日志框架——Log4j、Logback和SLF4J,并分析它们之间的关系和使用场景。 **Log4j** Log4j是Apache组织的一个开源项目,是Java世界中最先广泛使用的日志记录框架之一。它的核心功能...

    logback jar 包

    **日志框架——logback** 在Java开发中,日志记录是不可或缺的一部分,它帮助开发者追踪应用程序的运行状态,定位和解决潜在问题。Logback 是一个广泛应用的日志框架,由 Ceki Gülcü 创建,他是早期流行日志框架 ...

    slf4j+logback 于log4j+commons-logging大PK

    今天我们将聚焦两个流行的日志框架——SLF4J(Simple Logging Facade for Java)和Logback,以及它们与Log4j和Commons-Logging的对比。 SLF4J是一个接口层的日志框架,它提供一个统一的API,允许用户在运行时插入...

    Spring学习心得(二)——日志和数据库

    在本文中,我们将深入探讨这两个关键领域,并基于给出的"Spring学习心得(二)——日志和数据库"这个标题进行详细的阐述。 首先,让我们从日志开始。在Java应用程序中,日志系统扮演着调试、监控和问题排查的关键角色...

    slf4j + logback配置实例

    总结起来,SLF4J + Logback的组合提供了强大且灵活的日志记录能力。通过合理的配置,我们可以控制日志的级别、输出目的地以及格式,满足开发、调试、监控等多种需求。同时,SLF4J的API简单易用,使得日志记录成为...

    日志记录,开源项目使用方法见说明.zip

    Android系统自带了一个简单的日志系统——Log类,它提供了如Log.v(), Log.d(), Log.i(), Log.w() 和 Log.e()等方法,分别代表verbose, debug, info, warning 和 error级别的日志。这些方法用于输出不同严重程度的...

    java 日志发展

    在IT行业中,日志系统是软件开发不可或缺的一部分,它用于记录程序运行时的详细信息,帮助开发者追踪错误、排查问题以及优化系统性能。Java作为广泛使用的编程语言,其日志框架的发展历程反映了软件工程对日志处理的...

    ssm订单项目源码-附加数据库

    SSM订单项目源码是基于三大主流Java框架——Spring、SpringMVC和MyBatis构建的一个典型企业级应用。这个项目的核心目标是实现一个完整的订单处理系统,它涵盖了从用户下单到订单处理的整个流程。下面我们将深入探讨...

    log 牛

    【标题】:“日志分析——牛刀小试” 在IT行业中,日志分析是一项至关重要的技能,尤其是在系统维护、故障排查以及性能优化等方面。本文将以“log牛”为主题,探讨如何利用工具对日志进行深入分析,从而获取关键...

    对JDK log日志的扩展

    JDK内置的日志框架——java.util.logging(也称为 JUL)虽然简单易用,但在功能和灵活性上可能无法满足复杂的企业级需求。本文将深入探讨如何对JDK的日志进行扩展,以提升日志系统的效能和定制性。 首先,我们需要...

    Log4j.rar

    《深入理解Log4j——基于源码与工具的探索》 Log4j,作为Java世界里最著名的日志记录框架之一,一直以来都是开发者们必备的工具。它以其强大的功能、灵活的配置和高效的性能,赢得了广大开发者的青睐。这篇长文将...

    Jwebap User Guide 配置网站日志追踪

    1. **日志框架集成**:Jwebap支持多种流行的日志框架,如Log4j、Logback、Java Util Logging等。你需要根据项目中使用的日志框架选择相应的配置。 2. **日志级别配置**:在`jwebap.xml`中,可以为整个应用或特定的...

    日志详解

    本文将深入探讨“日志详解”这一主题,主要围绕着一个著名的日志框架——Log4j。 Log4j是Apache软件基金会开发的一个开源日志组件,广泛应用于Java编程语言。它提供了灵活的日志配置,使得开发者可以方便地控制日志...

    SSM项目调试——视频教学.rar

    此外,日志记录也是调试的重要辅助手段,你将了解到如何配置日志框架如Log4j或Logback,以便在开发过程中获取详细的运行信息。 视频教程还会涉及一些最佳实践,比如如何使用单元测试来验证代码的正确性,如何通过...

    TarenaLogs

    《TarenaLogs——Java学习中的日志记录与分析》 在Java编程中,日志记录是不可或缺的一部分,它能够帮助开发者在程序运行过程中追踪错误、调试代码,甚至用于性能分析。"TarenaLogs"这个主题,显然聚焦于Java学习中...

    [转]小黑之天地培训学习手记——第三、四周

    6. **日志分析工具**:Log4j、Logback等,配合ELK(Elasticsearch, Logstash, Kibana)堆栈,进行日志收集和分析。 7. **容器化工具**:Docker和Kubernetes,用于应用程序的快速部署和管理。 在第三、四周的学习中,...

Global site tag (gtag.js) - Google Analytics