0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

手动实现SpringBoot日志链路追踪

jf_ro2CN3Fa 来源:csdn 作者:CSDN 2022-12-15 15:04 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

  • 前言
  • 正文
3abca07e-7c3d-11ed-8abf-dac502259ad0.jpg

前言

从文章标题就知道,这篇文章是介绍些什么。

这是我一位朋友的问题反馈:

3b060c64-7c3d-11ed-8abf-dac502259ad0.png

好像是的,确实这种现象是普遍存在的。

有时候一个业务调用链场景,很长,调了各种各样的方法,看日志的时候,各个接口的日志穿插,确实让人头大。

模糊匹配搜索日志能解决吗? 能解决一点点。 但是不能完全呈现出整个链路相关的日志。

那要做到方便,很显然,我们需要的是把同一次的业务调用链上的日志串起来。

什么效果? 先看一个实现后的效果图:

3b14d7d0-7c3d-11ed-8abf-dac502259ad0.png

这样下来,我们再配合模糊匹配查找日志,效果不就刚刚的了。

cat-ninfo.log|grep"a415ad50dbf84e99b1b56a31aacd209c"

或者

grep-10'a415ad50dbf84e99b1b56a31aacd209c'info.log(10是指上下10行)

不多说,开整。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

正文

惯例,先看一眼这次实战最终工程的结构:

3b58db1a-7c3d-11ed-8abf-dac502259ad0.png

①pom.xml 依赖

<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>

<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.10version>
dependency>
dependencies>

②整合logback,打印日志,logback-spring.xml (简单配置下)


<configurationdebug="false">

<propertyname="log"value="D:/test/log"/>

<appendername="console"class="ch.qos.logback.core.ConsoleAppender">
<encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder">

<pattern>[%X{TRACE_ID}]%d{yyyy-MM-ddHHss.SSS}[%thread]%-5level%logger{50}-%msg%npattern>
encoder>
appender>

<appendername="file"class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<FileNamePattern>${log}/%d{yyyy-MM-dd}.logFileNamePattern>

<MaxHistory>30MaxHistory>
rollingPolicy>
<encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>[%X{TRACE_ID}]%d{yyyy-MM-ddHHss.SSS}[%thread]%-5level%logger{50}-%msg%npattern>
encoder>

<triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MBMaxFileSize>
triggeringPolicy>
appender>


<rootlevel="INFO">
<appender-refref="console"/>
<appender-refref="file"/>
root>
configuration>

application.yml

server:
port:8826
logging:
config:classpath:logback-spring.xml

③自定义日志拦截器 LogInterceptor.java

用途:每一次链路,线程维度,添加最终的链路ID TRACE_ID。

importorg.slf4j.MDC;
importorg.springframework.lang.Nullable;
importorg.springframework.util.StringUtils;
importorg.springframework.web.servlet.HandlerInterceptor;

importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.util.UUID;

/**
*@Author:JCccc
*@Date:2022-5-3010:45
*@Description:
*/
publicclassLogInterceptorimplementsHandlerInterceptor{

privatestaticfinalStringTRACE_ID="TRACE_ID";

@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){
Stringtid=UUID.randomUUID().toString().replace("-","");
//可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成
if(!StringUtils.isEmpty(request.getHeader("TRACE_ID"))){
tid=request.getHeader("TRACE_ID");
}
MDC.put(TRACE_ID,tid);
returntrue;
}

@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,
@NullableExceptionex){
MDC.remove(TRACE_ID);
}

}

MDC(Mapped Diagnostic Context)诊断上下文映射,是@Slf4j提供的一个支持动态打印日志信息的工具。

WebConfigurerAdapter.java 添加拦截器

importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
*@Author:JCccc
*@Date:2022-5-3010:47
*@Description:
*/
@Configuration
publicclassWebConfigurerAdapterimplementsWebMvcConfigurer{
@Bean
publicLogInterceptorlogInterceptor(){
returnnewLogInterceptor();
}

@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
registry.addInterceptor(logInterceptor());
//可以具体制定哪些需要拦截,哪些不拦截,其实也可以使用自定义注解更灵活完成
//.addPathPatterns("/**")
//.excludePathPatterns("/testxx.html");
}
}

ps: 其实这个拦截的部分改为使用自定义注解+aop也是很灵活的。

到这时候,其实已经完成,就是这么简单。

我们写个测试接口,看下效果:

@PostMapping("doTest")
publicStringdoTest(@RequestParam("name")Stringname)throwsInterruptedException{
log.info("入参name={}",name);
testTrace();
log.info("调用结束name={}",name);
return"Hello,"+name;
}
privatevoidtestTrace(){
log.info("这是一行info日志");
log.error("这是一行error日志");
testTrace2();
}
privatevoidtestTrace2(){
log.info("这也是一行info日志");

}

效果(OK的):

3b7c2340-7c3d-11ed-8abf-dac502259ad0.png

还没完。

接下来看一个场景, 使用子线程的场景:

故意写一个异步线程,加入这个调用里面:

3bb25adc-7c3d-11ed-8abf-dac502259ad0.png

再次执行看开效果,显然子线程丢失了trackId:

3bc97a14-7c3d-11ed-8abf-dac502259ad0.png

所以我们需要针对子线程使用情形,做调整,思路: 将父线程的trackId传递下去给子线程即可。

①ThreadPoolConfig.java 定义线程池,交给spring管理

importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.scheduling.annotation.EnableAsync;
importjava.util.concurrent.Executor;

/**
*@Author:JCccc
*@Date:2022-5-3011:07
*@Description:
*/
@Configuration
@EnableAsync
publicclassThreadPoolConfig{
/**
*声明一个线程池
*
*@return执行器
*/
@Bean("MyExecutor")
publicExecutorasyncExecutor(){
MyThreadPoolTaskExecutorexecutor=newMyThreadPoolTaskExecutor();
//核心线程数5:线程池创建时候初始化的线程数
executor.setCorePoolSize(5);
//最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(5);
//缓冲队列500:用来缓冲执行任务的队列
executor.setQueueCapacity(500);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("asyncJCccc");
executor.initialize();
returnexecutor;
}
}

② MyThreadPoolTaskExecutor.java 是我们自己写的,重写了一些方法:

importorg.slf4j.MDC;
importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

importjava.util.concurrent.Callable;
importjava.util.concurrent.Future;

/**
*@Author:JCccc
*@Date:2022-5-3011:13
*@Description:
*/
publicfinalclassMyThreadPoolTaskExecutorextendsThreadPoolTaskExecutor{
publicMyThreadPoolTaskExecutor(){
super();
}

@Override
publicvoidexecute(Runnabletask){
super.execute(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}


@Override
publicFuturesubmit(Callabletask){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}

@Override
publicFuturesubmit(Runnabletask){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}
}

③ThreadMdcUtil.java

importorg.slf4j.MDC;

importjava.util.Map;
importjava.util.UUID;
importjava.util.concurrent.Callable;

/**
*@Author:JCccc
*@Date:2022-5-3011:14
*@Description:
*/
publicfinalclassThreadMdcUtil{
privatestaticfinalStringTRACE_ID="TRACE_ID";

//获取唯一性标识
publicstaticStringgenerateTraceId(){
returnUUID.randomUUID().toString();
}

publicstaticvoidsetTraceIdIfAbsent(){
if(MDC.get(TRACE_ID)==null){
MDC.put(TRACE_ID,generateTraceId());
}
}

/**
*用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
*
*@paramcallable
*@paramcontext
*@param
*@return
*/
publicstaticCallablewrap(finalCallablecallable,finalMapcontext){
return()->{
if(context==null){
MDC.clear();
}else{
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try{
returncallable.call();
}finally{
MDC.clear();
}
};
}

/**
*用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
*
*@paramrunnable
*@paramcontext
*@return
*/
publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){
return()->{
if(context==null){
MDC.clear();
}else{
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try{
runnable.run();
}finally{
MDC.clear();
}
};
}
}

OK,重启服务,再看看效果:

3be73004-7c3d-11ed-8abf-dac502259ad0.png

可以看的,子线程的日志也被串起来了。



审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • spring
    +关注

    关注

    0

    文章

    345

    浏览量

    16134
  • 日志
    +关注

    关注

    0

    文章

    153

    浏览量

    11132
  • SpringBoot
    +关注

    关注

    0

    文章

    179

    浏览量

    747

原文标题:手动实现 SpringBoot 日志链路追踪,无需引入组件,日志定位更方便!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Linux系统日志的分析方法和排查技巧

    日志是运维工程师排查问题的第一手资料。当服务器出现异常时,系统日志、应用日志、安全日志中往往隐藏着问题的答案。然而,很多工程师面对海量的日志
    的头像 发表于 05-14 10:44 552次阅读

    为什么无法刷新 MCU 固件?

    了MCU探针,并尝试使用安全配置工具来刷新MCU固件。但写入映像选项已被禁用。我不确定自己遗漏了哪些步骤。在此方面,恳请得到您的帮助。
    发表于 04-30 07:06

    NXP数字钥匙 3.0-真正实现手动汽车门禁

    NXP数字钥匙3.0-真正实现手动汽车门禁
    发表于 04-14 14:11 1次下载

    SDV域控器日志追踪与解析技术 – DLT

    从研发到测试SDV域控制器的调试日志在汽车软件复杂度不断攀升的今天,对不同核或分区上运行的复杂软件进行调试或追踪极具挑战性,并且在POSIX系统或车辆上的复杂软件进行分步调试往往更具挑战。所以
    的头像 发表于 01-21 10:04 2171次阅读
    SDV域控器<b class='flag-5'>日志</b><b class='flag-5'>追踪</b>与解析技术 – DLT

    模组日志功能技术概览

    模组日志功能技术方案以低侵入、高可用为原则,提供统一的日志API、多级日志分类与条件输出机制。通过集成该技术,开发者可在不干扰业务逻辑的前提下,全面掌握模组的执行状态与异常行为。 一、本文讨论的边界
    的头像 发表于 01-14 15:32 335次阅读
    模组<b class='flag-5'>日志</b>功能技术概览

    BLE SoC,如何助力管理者实现高效的资产追踪

    基于BLE SoC的资产追踪方案,具备低功耗、高集成和生态成熟,实现高效资产定位与管理。
    的头像 发表于 12-22 14:16 539次阅读
    BLE SoC,如何助力管理者<b class='flag-5'>实现</b>高效的资产<b class='flag-5'>追踪</b>?

    RT-Thread ULOG: 创建多个文件后端并保存不同日志方法 | 技术集结

    目录前言使用场景实现功能具体操作1前言在项目开发中需要使用到日志功能来调试和查看问题。有些问题并不会在我们实时查看的时候发生,而是在你上个厕所的功夫可能就发生了。如果上位机的缓冲区不够大,可能错误
    的头像 发表于 12-15 19:22 5332次阅读
    RT-Thread ULOG: 创建多个文件后端并保存不同<b class='flag-5'>日志</b>方法 | 技术集结

    电能质量在线监测装置数据日志能加密存储吗?

    加密的技术实现方式 1. 加密算法与分级策略 日志类型 推荐加密算法 密钥管理方式 适用场景 敏感日志(事件记录、故障数据、用户信息) AES-256 (高级加密标准) 硬件安全模块(HSM/TPM/SE)管理密钥 电网关口、新
    的头像 发表于 12-05 10:16 874次阅读
    电能质量在线监测装置数据<b class='flag-5'>日志</b>能加密存储吗?

    如何使用SpringBoot、Vue2.0、MySQL开发一套云诊所系统?

    (RESTful)等。 对于云诊所系统,SpringBoot可以用于实现患者管理、预约挂号、电子病历、药品管理、收费管理等核心功能。 前端:V
    的头像 发表于 11-27 16:02 516次阅读
    如何使用<b class='flag-5'>SpringBoot</b>、Vue2.0、MySQL开发一套云诊所系统?

    电能质量在线监测装置的备用切换机制是否支持远程控制?

    现代电能质量在线监测装置的备用切换机制 普遍支持远程控制 ,主流厂商通过协议指令、软件平台和硬件冗余设计,实现了从状态查询、策略配置
    的头像 发表于 11-06 16:48 1679次阅读

    电能质量在线监测装置的备用切换需要手动干预吗?

    电能质量在线监测装置的备用切换 通常无需手动干预 ,主流设备通过硬件冗余设计、协议层自动切换机制和智能算法实现全流程自动化。以下是关键实现
    的头像 发表于 11-06 16:29 1544次阅读

    资源状态感知是如何实现对网络状态的实时感知的?

    资源状态感知对网络状态的实时监测是通过硬件底层检测、协议层交互、算法模型分析的多层协同实现的,具体技术路径如下: 一、硬件层:物理信号的实时捕获 PHY 芯片的直接感知以太网 PHY 芯片(如
    的头像 发表于 11-06 14:49 991次阅读

    目标追踪的简易实现:模板匹配

    一、目标追踪和图像识别 一般来说,提到机器视觉这个概念都会想到图像识别,比如人脸识别、文本识别等等,目标追踪这个概念在平时接触的相对比较少。但实际上,目标追踪可以理解为图像识别的动态过程:图像识别
    发表于 10-28 07:21

    拼多多商品推广链接生成API:社交裂变的转化追踪利器

    ​ 在社交电商时代,拼多多通过 商品推广链接生成API 为商家提供了精准的流量转化解决方案。该工具不仅简化了社交裂变活动的落地,更实现了全转化效果追踪,成为提升营销效率的核心引擎。
    的头像 发表于 09-08 16:22 1119次阅读
    拼多多商品推广链接生成API:社交裂变的转化<b class='flag-5'>追踪</b>利器

    如何使用树莓派与OpenCV实现面部和运动追踪的云台系统?

    大家好,这是一个树莓派和OpenCV的连载专题。使用树莓派与OpenCV实现姿态估计和面部特征点追踪使用树莓派与OpenCV实现面部和运动追踪的云台系统使用树莓派和OpenCV
    的头像 发表于 08-14 17:45 2940次阅读
    如何使用树莓派与OpenCV<b class='flag-5'>实现</b>面部和运动<b class='flag-5'>追踪</b>的云台系统?