logback异步日志配置与源码解读

如何在项目中使用异步日志,以及它的实现原理

Spring Boot 项目默认使用的日志工具是 logback,它的配置文件是 org.springframework.boot.logging.logback 包下的 base.xml 文件

<?xml version="1.0" encoding="UTF-8"?>

<!--
Base logback configuration provided for compatibility with Spring Boot 1.1
-->

<included>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</included>

配置异步日志,要在 resources 目录下添加 logback-spring.xml 文件,复用了 Spring Boot 的一些默认的配置,只使用控制台输出的Appender 打印日志。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <appender name="ASYNC_CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>2048</queueSize>
        <appender-ref ref="CONSOLE" />
    </appender>
    <root level="INFO">
        <appender-ref ref="ASYNC_CONSOLE" />
    </root>
</configuration>

参数说明:
discardingThreshold:默认值为-1,当queueSize的剩余空间小于该容量的20%的时候,会丢弃TRACE, DEBUG 和 INFO级别的日志,只保留WARN 和 ERROR日志。如果要保证不丢失日志,可以设置该值为0。
queueSize:异步日志使用 BlackingQueue 缓存日志事件,此参数表示日志缓存的大小,默认值256
maxFlushTime:当LoggerContext停止时, AsyncAppender 的stop 方法等待worker完成日志刷新任务的最大超时时间,默认值为1000毫秒
neverBlock:默认值为false,表示当缓存队列放不下日志的时候会阻塞住。设置为true时,表示会丢弃日志,不会阻塞线程。
includeCallerData:是否包含调用者数据,默认值为false,默认线程名称和MDC会拷贝。

异步日志的原理:在日志输出的时候不会直接输出日志到磁盘,而是将日志存进一个 Queue 的队列里,另外起线程消费队列里的日志。

Spring Boot 默认的配置文件在 spring-boot.jar 的 logging 目录下,它有以下几个核心概念:

logback配置文件的核心概念
  • 概念
    root: 根 logger (所有 logger 都继承它的属性)
    logger:定义某个包的日志级别
    appender:定义日志输出的方式

同步日志输出时的时序图

同步日志输出时的时序图

异步日志输出的时序图

异步日志输出的时序图

ch.qos.logback.core.AsyncAppenderBase部分源码解读

protected void append(E eventObject) {
    if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) {
        return;
    }
    preprocess(eventObject);
    put(eventObject);
}

AsyncAppender继承了AsyncAppenderBase,所以异步的Appenderappend方法会调用put方法。

private void put(E eventObject) {
    if (neverBlock) {
        blockingQueue.offer(eventObject);
    } else {
        putUninterruptibly(eventObject);
    }
}

这段代码说明当neverBlacktrue时,如果队列已满的话,会丢失日志。

然后看下ch.qos.logback.core.AsyncAppenderBase$Worker,这个内部类是,异步日志缓存队列的消费者。

class Worker extends Thread {

    public void run() {
        AsyncAppenderBase<E> parent = AsyncAppenderBase.this;
        AppenderAttachableImpl<E> aai = parent.aai;

        // loop while the parent is started
        while (parent.isStarted()) {
            try {
                E e = parent.blockingQueue.take();
                aai.appendLoopOnAppenders(e);
            } catch (InterruptedException ie) {
                break;
            }
        }

        addInfo("Worker thread will flush remaining events before exiting. ");

        for (E e : parent.blockingQueue) {
            aai.appendLoopOnAppenders(e);
            parent.blockingQueue.remove(e);
        }

        aai.detachAndStopAllAppenders();
    }
}

AsyncAppenderBase.this获取Worker的外部类对象,从而获取到异步Appender引用的Appender,消费者线程使用引用的Appender打印日志。可以看到这个线程其实就是一个while循环,当JVM进程接收到停止信号时,消费者线程会被打断,跳出死循环,接着会取出队列中剩余日志消费掉。这个注意一个参数maxFlushTime
ch.qos.logback.core.AsyncAppenderBase#stop

public void stop() {
    if (!isStarted())
        return;

    // mark this appender as stopped so that Worker can also processPriorToRemoval if it is invoking
    // aii.appendLoopOnAppenders
    // and sub-appenders consume the interruption
    super.stop();

    // interrupt the worker thread so that it can terminate. Note that the interruption can be consumed
    // by sub-appenders
    worker.interrupt();

    InterruptUtil interruptUtil = new InterruptUtil(context);

    try {
        interruptUtil.maskInterruptFlag();

        worker.join(maxFlushTime);

        // check to see if the thread ended and if not add a warning message
        if (worker.isAlive()) {
            addWarn("Max queue flush timeout (" + maxFlushTime + " ms) exceeded. Approximately " + blockingQueue.size()
                            + " queued events were possibly discarded.");
        } else {
            addInfo("Queue flush finished successfully within timeout.");
        }

    } catch (InterruptedException e) {
        int remaining = blockingQueue.size();
        addError("Failed to join worker thread. " + remaining + " queued events may be discarded.", e);
    } finally {
        interruptUtil.unmaskInterruptFlag();
    }
}

这段代码说明,maxFlushTime表示的是当消费者线程接收到打断信号,多久以后会清理消费者线程的内存空间。默认是1秒,也就是说当JVM进程收到停止信号,给消费者线程处理剩余日志的时间最大为1秒。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351