使用logback异步打印日志

news2024/9/23 9:28:25

文章目录

  • 一、介绍
  • 二、运行环境
  • 三、演示项目
    • 1. 接口
    • 2. 日志配置文件
    • 3. 效果演示
    • 4. 异步输出验证
  • 四、异步输出原理
  • 五、其他参数配置
  • 六、源码分析
    • 1. 同步输出
    • 2. 异步输出
  • 七、总结

一、介绍

对于每一个开发人员来说,在业务代码中添加日志是至关重要的,尤其是后端开发,如果不打印日志,在接口出现bug的时候将无法定位bug;有了日志,即使接口出现bug,也可以通过查询日志很快的定位到bug的位置。

使用springboot开发的朋友使用最多的日志框架想必都是logback了吧,毕竟它是springboot官方推荐的日志框架,与springboot天然整合。再结合第三方工具lombok,当我们需要打印日志时只需要通过log.info("第一条日志");log.error("日志信息:{}", arg1);简单的代码即可完成打印。


如果接口中出现大量日志,且日志是直接输出到日志文件通过网络发送到其他服务器呢?这种场景下,将会出现较为耗时的磁盘IO网络IO,从而导致接口响应时间变长。

有没有办法通过异步的形式将日志打印出来呢?答案是肯定的,logback早就想到这一点了。请往下看。

二、运行环境

  • springboot:2.4.3
  • logback:1.2.3。logback是springboot自带的默认日志框架

该版本的springboot中自带该版本的logback,因此不必单独引入logback的依赖。

三、演示项目

本项目仅用于logback日志的异步打印,因此较为简单,仅包含一个接口和logback日志配置文件。

1. 接口

@Slf4j
@RestController
@RequestMapping("/student")
public class StudentController {

    @GetMapping("/insert")
    public String insert() {
        log.info("第一条日志");
        log.info("第二条日志");
        log.info("第三条日志");
        log.info("第四条日志");
        log.info("第五条日志");
        return "成功";
    }
}

2. 日志配置文件

logback提供了大量的Appender将日志输出到指定的位置。如ConsoleAppender用于同步地将日志输出到控制台FileAppender用于同步地将日志输出到指定日志文件SocketAppender用于同步地将日志通过套接字发送到指定服务器。他们都有一个共同点:同步

那么如何修改日志配置文件将日志输出改为异步呢?logback提供了一个Appender的异步实现类:AsyncAppender。使用方法也非常简单,同样也是通过在配置文件中添加<appender>标签声明AsyncAppender,然后通过引用属性引用一个同步Appender即可,如下所示

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <!-- 日志输出格式 -->
    <property name="log.pattern"
              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)"/>

    <!-- 同步输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 异步输出 -->
    <appender name="async-console" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 引用同步,将其包装为异步 -->
        <appender-ref ref="console" />
    </appender>

    <!-- 采用异步输出 -->
    <root level="INFO">
        <appender-ref ref="async-console" />
    </root>
    
</configuration>

3. 效果演示

我们使用postman调用接口,得到返回结果,如下图所示

在这里插入图片描述

查看控制台打印的日志,如下图所示

在这里插入图片描述

可这如何看出该日志打印和接口业务代码为异步的呢?

4. 异步输出验证

我们在AppenderAttachableImpl类的appendLoopOnAppenders()方法上添加断点,且要将该端点设置为多线程模式,如下图所示

在这里插入图片描述

此时再次调用接口,得到相同的结果,如下图所示

在这里插入图片描述

虽然接口已经正常响应了,但是我们发现代码却进入了断点,从该方法参数可以看出,在将要打印第一条日志时进入断点。如下图所示

在这里插入图片描述

且控制台上没有输出任何日志

在这里插入图片描述

当我们逐次放开断点使代码继续执行时,日志才逐行打印出来。因此我们可以断定接口业务代码与日志的打印为异步执行。

四、异步输出原理

为什么给同步Appender包装一层AsyncAppender就可以实现异步输出呢?

AsyncAppender内部维护了一个阻塞队列ArrayBlockingQueue,logback将我们打印日志的代码log.info("第一条日志")封装为一个事件Event,每当我们需要打印一行日志时,logback会将该事件放在阻塞队列中,然后再通过多线程的形式从该阻塞队列中获取一个事件执行。

阻塞队列可以保证日志的输出是有序的,多线程保证日志的输出是异步的。

五、其他参数配置

AsyncAppender能实现异步输出日志的关键因素是阻塞队列多线程,既然说到阻塞队列,那么必然少不了相关的参数。

参数类型说明
queueSizeint阻塞队列的最大容量,默认为256
discardingThresholdint默认地,当阻塞队列剩余容量达到20%时,logback将丢弃trace, debug和info三个级别的日志,而只输出warn和error级别的日志。如果需要全部输出,则将该值设置为0。
includeCallerDataboolean如果为true,logback对日志封装的事件中将包含更多调用者和父线程的信息,将降低性能。默认为false
maxFlushTimeint当服务停止时,logback将会等待一段时间,在这段时间内将正在输出的日志完成。
neverBlockboolean当阻塞队列已满时,如果为true,logback将丢失日志时间。默认为false

配置文件如下所示

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <!-- 日志输出格式 -->
    <property name="log.pattern"
              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)"/>

    <!-- 同步输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 异步输出 -->
    <appender name="async-console" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 阻塞队列的参数配置 -->
        <queueSize>256</queueSize>
        <discardingThreshold>20</discardingThreshold>
        <includeCallerData>false</includeCallerData>
        <maxFlushTime>1000</maxFlushTime>
        <neverBlock>false</neverBlock>
        
        <!-- 引用同步,将其包装为异步 -->
        <appender-ref ref="console" />
    </appender>

    <!-- 采用异步输出 -->
    <root level="INFO">
        <appender-ref ref="async-console" />
    </root>
    
</configuration>

六、源码分析

在logback中,任何日志的输出去都是通过start()方法完成的,接下来我们以ConsoleAppender同步输出 和 AsyncAppender异步输出为例,从源码层面分析。

1. 同步输出

进入ConsoleAppender源码,查看其start()方法。

在这里插入图片描述

可以看到其中第一行代码中通过target.getStream()获取输出目标的流,而target属性的声明如下所示,从该声明中我们看到通过target.getStream()获取输出目标的流就是System.out.print()了。这下我们就明白ConsoleAppender是如何将日志输出到控制台了。

在这里插入图片描述

再进入setOutputStream()方法查看其具体实现,从中可以看到是通过lock.lock()lock.unlock()来保证其同步执行的,其中lock所是一个非公平的可重入同步锁,这是多线程中同步锁AQS体系的概念,这里不详细展开,我们只需要知道通过该锁实现同步即可。

在这里插入图片描述

2. 异步输出

进入AsyncAppender源码,查看其start()方法,该方法定义在其父类AsyncAppenderBase中。

在这里插入图片描述

在该方法中我们看到第102行代码使用queueSize参数声明一个指定容量的阻塞队列,默认的discardingThreshold参数为阻塞队列容量的1/5,即20%,然后就是对worker属性进行设置,然后调用其start()方法。worker实际上是Thread类的子类Worker对象,调用其start()方法就是通过一个新线程调用该对象的run()方法。

在这里插入图片描述

从该方法中我们发现,无论是直接调用take()方法获取阻塞队列中的元素,还是通过遍历阻塞队列获取其中的元素,都会调用appendLoopOnAppenders()方法,而该方法就是logback真正输出日志的方法,也是我们在验证其异步输出是打断点的地方。

七、总结

  • logback通过AyncAppender实现日志的异步输出。
  • AyncAppender异步输出的原理是通过阻塞队列多线程实现的。


纸上得来终觉浅,绝知此事要躬行。

————————我是万万岁,我们下期再见————————

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/855146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS:弹性盒子模型详解(用法 + 例子 + 效果)

目录 弹性盒子模型flex-direction 排列方式 主轴方向换行排序控制子元素缩放比例缩放是如何实现的&#xff1f; 控制子元素的对其方式justify-content 横向 对齐方式align-items 纵向 对齐方式 align-content 多行 对齐方式 弹性盒子模型 flex-direction 排列方式 主轴方向 f…

webshell免杀项目-Auto-JSPwebshell(五)

Auto-JSPwebshell/jsp免杀/webshell免杀/自动生成 项目地址&#xff1a; https://github.com/G0mini/Bypass 具体使用请参考&#xff1a; https://mp.weixin.qq.com/s/9-__B0MBRSXHla6O0KU7Gg

PCB制造中铜厚度的重要性

电子产品中的PCB是现代电子设备中不可或缺的一部分。在PCB制造过程中&#xff0c;铜厚度是一个非常重要的因素。正确的铜厚度可以保证电路板的质量和性能&#xff0c;同时也影响着电子产品的可靠性和稳定性。 一般我们常见的铜厚有17.5um&#xff08;0.5oz&#xff09;&#x…

SpringBootWeb案例-准备工作

目录 前言 准备工作 需求&环境搭建 需求 环境搭建 开发规范 Restful开发规范 统一的响应结果 开发流程 前言 根据过往的文章可以知道目前我已经学习完了前端、后端、数据库的基础知识&#xff0c;接下来通过一个基于SpringBoot工程开发的web项目案例。 准备工作 …

来讲一讲面试必问的异步FIFO设计!

异步FIFO设计可以说是数字IC设计工程师面试时必问的一个问题了&#xff0c;也是我们经常使用但是又往往被忽略的一个东西&#xff0c;今天就展开详细说一说不同深度&#xff08;2^N或者非2^N&#xff09;异步FIFO的设计思想&#xff1b; 一&#xff1a;2^N深度异步FIFO设计 1…

Unity开发笔记:截取指定位置含有UI的场景截图并输出

学习记录整理&#xff0c;自用&#xff0c;也希望能帮助到有相同需求的人。 如果直接截全图&#xff1a; string screenshotName "Assets/Textures/UI/20230803/2.png";ScreenCapture.CaptureScreenshot(screenshotName);截取指定位置含有UI的场景截图&#xff1a; …

Therac-25事故:软件缺陷引发的医疗灾难与教训

目录 引言 Therac-25&#xff1a;背景与功能 软件缺陷导致的灾难 Bug原理解析与编程人员的反思 教训与反思 结论 引言 在计算机科技的进步与应用领域&#xff0c;软件的质量和安全性至关重要。然而&#xff0c;历史上曾经发生过一系列令人震惊的事件&#xff0c;突显了软…

培训报名小程序报名功能完善

目录 1 修改数据源2 修改表单3 支付成功时修改状态4 创建报名成功页5 最终的效果总结 目前我们的报名功能已经搭建了一个基础版&#xff0c;后续需要展示用户已经报名的信息&#xff0c;需要添加一个状态来显示用户是否成功付费。 1 修改数据源 打开我们的报名数据源&#xff…

google PGS 下一代id

前言&#xff1a;为了进一步增强用户的隐私及其多平台游戏体验&#xff0c;Play 游戏服务(PGS) 正在推出下一代玩家 ID&#xff0c;用户第一次玩游戏时&#xff0c;他们将始终被分配一个唯一的下一代玩家 ID&#xff0c;无论用户在什么设备或平台上玩游戏&#xff0c;该 ID 都将…

加入[无人驾驶吕同学]Apollo专属课程领礼品啦!

号外号外&#xff0c;无人驾驶吕同学的Apollo领航官专属课程已经上线了。用户完成课程50%即可领取专属福利&#xff08;百度限定鼠标垫、Apollo限量帆布包、Apollo六周年定制钥匙扣、Apollo六周年限定徽章&#xff09;&#xff0c;四种礼品任选其一。 课程链接&#xff1a;htt…

使用 Docker 和 Streamlit 构建和部署 LangChain 支持的聊天应用程序

文章目录 前言聊天应用程序组件和技术LangChain Python框架开放人工智能模型前端 Streamlit UI使用 Docker 进行部署Docker 优化以实现轻量级和快速构建Docker-compose.yaml 文件基础设施Streamlit 公共云谷歌应用引擎使用 Google Cloud Run 部署应用1.启动服务2. 创建角色并将…

水产养殖产量低?教你个万能的方法!

水产养殖业作为重要的食品生产和经济活动之一&#xff0c;为人们提供了丰富的水产品&#xff0c;但如果不加以适当监测和管理&#xff0c;也可能对水质和生态系统造成负面影响。因此&#xff0c;水产养殖用水监测显得尤为重要。 在养殖过程中&#xff0c;水质的优劣直接影响着养…

Linux小型操作系统项目,《操作系统真象还原》第三章——完善MBR

前引 上一章我们完成了MBR的雏形编写&#xff0c;但是只打印了几个字符&#xff0c;这一章我们才要真正地去完成MBR的功能。 在完成MBR的功能之前我们要先了解一些知识&#xff0c;首先介绍什么是实模式。 书上的内容实在繁杂&#xff0c;简单地说&#xff0c;实模式没有虚拟和…

【算法|数组】双指针

算法|数组——双指针 引入 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;…

77. 组合

题目链接&#xff1a;力扣 解题思路&#xff1a; AC代码 class Solution {List<Integer> tem new ArrayList<>();List<List<Integer>> result new ArrayList<>();public List<List<Integer>> combine(int n, int k) {process(n…

重试框架入门:Spring-RetryGuava-Retry

前言 在日常工作中&#xff0c;随着业务日渐庞大&#xff0c;不可避免的涉及到调用远程服务&#xff0c;但是远程服务的健壮性和网络稳定性都是不可控因素&#xff0c;因此&#xff0c;我们需要考虑合适的重试机制去处理这些问题&#xff0c;最基础的方式就是手动重试&#xf…

Vue3:页面A搜索后跳转到页面B,然后从页面B退回页面A重新搜索,但是得到的页面B得刷新一下才会显示正确的数据

问题 Vue3&#xff1a; 从页面A进行搜索后跳转到页面B&#xff0c;然后从页面B退回页面A重新搜索&#xff0c;但是得到的页面B得刷新一下才会显示正确的数据。 读取数据的代码格式大致如下&#xff08;代码做了一些删减&#xff09;&#xff1a; 解决 会出现上述情况&#…

DIY 一个 Docker Base Image

1. 我们先使用C语言写一个hello-world程序 vim hello.c # include <stdio.h>int main() {print("hello docker\n"); } 2. 将hello.c文件编译成二进制文件, 需要安装工具 yum install gcc yum install glibc-static 开始编译 gcc -static hello.c -o hello 编译…

低代码平台“高”效率开发的5个能力

迫于智改数转的压力&#xff0c;促使企业要不停地思考如何从低代码的角度释放一些重复枯燥又高成本的人力投入。历经多次重大重构及大量项目实战验证之后&#xff0c;个人认为一款高效率的低代码开发平台&#xff0c;应有以下几点能力。 一、低代码平台“高”效率开发的5个能力…

ES索引重建reindex详解

目录 一、使用场景 二、reindex介绍 三、使用手册 1、覆盖更新 2、创建丢失的文档并更新旧版本的文档 3、仅创建丢失的文档 4、冲突处理 5、source中添加查询条件 6、source中包含多个源索引 7、限制处理的记录数 8、从远程ES集群中重建索引 9、提取随机子集 10、…