使用logback异步打印日志
文章目录
- 一、介绍
- 二、运行环境
- 三、演示项目
- 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
能实现异步输出日志的关键因素是阻塞队列和多线程,既然说到阻塞队列,那么必然少不了相关的参数。
参数 | 类型 | 说明 |
---|---|---|
queueSize | int | 阻塞队列的最大容量,默认为256。 |
discardingThreshold | int | 默认地,当阻塞队列剩余容量达到20%时,logback将丢弃trace, debug和info三个级别的日志,而只输出warn和error级别的日志。如果需要全部输出,则将该值设置为0。 |
includeCallerData | boolean | 如果为true,logback对日志封装的事件中将包含更多调用者和父线程的信息,将降低性能。默认为false。 |
maxFlushTime | int | 当服务停止时,logback将会等待一段时间,在这段时间内将正在输出的日志完成。 |
neverBlock | boolean | 当阻塞队列已满时,如果为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
异步输出的原理是通过阻塞队列和多线程实现的。
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————
相关文章:

使用logback异步打印日志
文章目录 一、介绍二、运行环境三、演示项目1. 接口2. 日志配置文件3. 效果演示4. 异步输出验证 四、异步输出原理五、其他参数配置六、源码分析1. 同步输出2. 异步输出 七、总结 一、介绍 对于每一个开发人员来说,在业务代码中添加日志是至关重要的,尤…...

ArcGIS Pro暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用
GIS是利用电子计算机及其外部设备,采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲,它是在一定的地域内,将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来,达到对地理和属性信息的综合管理。GIS的…...

Rabbitmq的消息确认
配置文件 spring:rabbitmq:publisher-confirm-type: correlated #开启确认回调publisher-returns: true #开启返回回调listener:simple:acknowledge-mode: manual #设置手动接受消息消息从生产者到交换机 无论消息是否到交换机ConfirmCallback都会触发。 Resourceprivate Rabb…...

在飞机设计中的仿真技术
仿真技术在飞机设计中发挥着越来越重要的作用,本文阐述了国内外在飞机设计中广泛使用的结构强度计算,多体动力学仿真、多学科多目标结构优化、内外流场分析、非线性有限元分析、疲劳强度分析、电磁仿真分析,机电液联合仿真分析等,…...

(2023Arxiv)Meta-Transformer: A Unified Framework for Multimodal Learning
论文链接:https://arxiv.org/abs/2307.10802 代码链接:https://github.com/invictus717/MetaTransformer 项目主页:https://kxgong.github.io/meta_transformer/ 【注】:根据实验结果来看,每次输入一种数据源进行处…...
解决Python读取图片路径存在转义字符
普遍解决路径中存在转义字符的问题的方法 普遍解决转义字符的问题,无非是以下这三种。 一、在路径前添加r 直接在路径前面加r,这种方法能够使字符保持原始的意思。 比如下面这种: pathr"D:\MindSpore\Dearui\source\ces\0AI.png&qu…...

Windows 安装 pandoc 将 jupyter 导出 pdf 文件
Windows 安装 pandoc 将 jupyter 导出 pdf 文件 1. 下载 pandoc 安装文件2. 安装 pandoc3. 安装 nbconvert4. 使用 pandoc 1. 下载 pandoc 安装文件 访问 https://github.com/jgm/pandoc/releases,下载最新版安装文件,例如,3.1.6.1 版&#…...

混合云环境实现K8S可观测的6大策略
2023年,原生云应用及平台发展迅猛。大量企业都在努力发挥其应用程序的最大潜力,以确保极致的用户体验并推动业务增长。 混合云环境的兴起和容器化技术(如Kubernetes)的采用彻底改变了现代应用程序的开发、部署和扩展方式。 在这个数字舞台上,…...

音视频 FFmpeg命令行搭建
文章目录 一、配置二、测试 一、配置 以FFmpeg4.2.1 win32为例 解压ffmpeg-4.2.1-win32-shared.zip 拷⻉可执⾏⽂件到C:\Windows拷⻉动态链接库到C:\Windows\SysWOW64 注:WoW64 (Windows On Windows64)是⼀个Windows操作系统的⼦系统,被设计⽤来处理许…...
ORACLE wallet实现无需输入用户名与密码登陆数据库 注意修改目录权限
wallet权限 linux 777 windows 需要修改.lck文件的owner 在ORACLE 10G前,我们在SHELL或JDBC中连接数据库时,都需要输入用户名与密码,并且都是明文。从1OGR2开始,ORACLE提供wallet这个工具,可以实现无需输入用户名与密…...

linux - 用户权限
认知root用户 无论是Windows、Macos、Linux均采用多用户的管理模式进行权限管理 在Linux系统中,拥有最大权限的账户名为: root(超级管理员) root用户拥有最大的系统操作权限,而普通用户在许多地方的权限是受限的。普通用户的权限,一般在其HOME目录内是不…...

计蒜客T1115——字符串判等
水题不解释,考研复习压力偶尔写一道换换心情还不错~ 这里有一个比较有趣的知识点,对于同时输入多个字符串时还要允许空格的输入,那么普通的cin函数就不能满足要求了,这里采用getline函数解决,如下: string …...
Android Framework工具——EA画图
EA 是一个著名的企业架构(Enterprise Architecture)工具,用于绘制和管理企业的架构图和过程模型。该工具提供了多种功能,包括建立业务流程图、数据流图、组织结构图、应用架构图等。EA工具可帮助企业进行战略规划、业务流程改进和系统开发等活动。 一、时序图 时序图(Seq…...

使用MyEclipse如何部署Descriptor (XML)编辑器?
Descriptor (XML) Editor编辑器包含了高级的XML编辑功能,在本文中您将了解到这些编辑功能、Web XML编辑等,此功能包含在MyEclipse中可用。 MyEclipse v2023.1.2离线版下载 1. Web XML 编辑器 MyEclipse Web XML编辑器包括高级XML编辑功能,…...

Codeforces Round 889 (Div. 2)C题题解
文章目录 [Dual (Hard Version)](https://codeforces.com/contest/1855/problem/C2)问题建模问题分析1.按元素值分类讨论,正负不同时存在时2.若正负同时存在时代码 Dual (Hard Version) 问题建模 给定n个数,n不超过20,且每个数ai,…...

无涯教程-Perl - Subroutines(子例程)
定义子程序 Perl编程语言中 Subroutine子程序定义的一般形式如下: sub subroutine_name {body of the subroutine } 调用该Perl Subroutine的典型方式如下- subroutine_name( list of arguments ); 在Perl 5.0之前的版本中,调用 Subroutine的语法略有不同&…...

Rpc异步日志模块
Rpc异步日志模块作用 在一个大型分布式系统中,任何部署的分布式节点都可能发生崩溃,试想如果用普通的办法,即先排查哪个节点down掉了,找到down掉的节点后采取调试工具gdb调试该节点,进而排查宕机的原因。这中排查方法…...
python-pip
pip 路径 python 下载后自带pip ,在scripts 下,如 D:\install\python\Scripts numpy pip3 install numpy scipy matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simplepandas D:\install\python\Scripts>pip3 install pandas -i https://pypi.tuna.tsingh…...

无涯教程-Perl - getppid函数
描述 该函数返回父进程的进程ID。 语法 以下是此函数的简单语法- getppid返回值 该函数返回父进程的进程ID。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perl$ppidgetppid();print "Parent Process ID $ppid\n";执行上述代码后,将产生以下输出- Paren…...
AUTOSAR规范与ECU软件开发(基础篇)1.2 汽车电子控制系统的基本构成
目录 前言 1、 传感器 2、 电子控制单元(ECU) 3、 执行器 前言 汽车电子控制系统主要由传感器(Sensor) 、 电子控制单元(Electronic Control Unit, ECU) 和执行器(Actuator) 组成(图1.1) ,对被控对象(Controlled Object)...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...

Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...