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

    文章

    340

    浏览量

    14341
  • 日志
    +关注

    关注

    0

    文章

    138

    浏览量

    10641
  • SpringBoot
    +关注

    关注

    0

    文章

    173

    浏览量

    177

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

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

收藏 人收藏

    评论

    相关推荐

    静态路由和聚合的通信原理

    静态路由和聚合的通信原理
    发表于 12-23 16:59 0次下载

    负载均衡设置在哪里?

    负载均衡设置涉及交换机、路由器和(可选)负载均衡器的设置。首先规划网络拓扑和IP地址,备份设备配置。然后,在交换机上配置VLAN和Trunk,在路由器上配置接口、路由协议和策
    的头像 发表于 11-13 10:19 131次阅读

    nginx日志配置方法

    access_log用来定义日志级别,日志位置。
    的头像 发表于 10-24 17:43 228次阅读

    日志篇:模组日志总体介绍

    ​今天我们学习合宙模组日志总体介绍,以下进入正文。 一、本文讨论的边界 本文是对合宙 4G 模组, 以及 4G+GNSS 模组的日志功能的总体介绍。通过日志,可以对研发过程中,以及模组运行过程中
    的头像 发表于 10-24 07:16 192次阅读
    <b class='flag-5'>日志</b>篇:模组<b class='flag-5'>日志</b>总体介绍

    PCle培训概述

    电子发烧友网站提供《PCle培训概述.pdf》资料免费下载
    发表于 09-11 09:16 0次下载
    PCle<b class='flag-5'>链</b><b class='flag-5'>路</b>培训概述

    IR615如何实现VPN备份?

    目的:IR615的备份(WAN为主、Wi-Fi做STA为从),当VPN建好后,WAN
    发表于 07-25 08:27

    IG网关产品实现备份的方法

    的-接口备份模块。 3、选择主接口以及备份接口等数据参数;设定完成后添加即生效。(Dot11radio 1代表WLAN接口) 通过这样设定即可实现备份的基础功能设定:
    发表于 07-24 08:25

    奇怪!应用的日志呢??

    1. 问题回顾 问题背景 是在进行中台应用中间件迁移过程中,发现存在 项目启动失败 或者 项目正常启动 (jsf正常挂载并正常运行,mq正常发送和消费)但是 无任何日志打印 现象。 更奇怪 的是不打
    的头像 发表于 06-11 10:48 315次阅读
    奇怪!应用的<b class='flag-5'>日志</b>呢??

    如何识别光纤问题?

    光纤网络专为连续运行而设计。通常,光纤网络以最佳效率运行。然而,网络中有时会遇到光纤问题。由于光纤网络的复杂性,这些光纤问题很难识别。然而,为了确保光纤网络的最佳性能,识别和解
    的头像 发表于 06-11 10:12 553次阅读

    加法进位手动约束

    在激光雷达中,使用FPGA实现TDC时需要手动约束进位的位置。这里简单记录下。 在outflow下会生成一个.qplace文件 。用于指示布线的各个原语资源的分布位置 。 它的内容主是 是原语
    的头像 发表于 05-20 11:38 1308次阅读
    加法进位<b class='flag-5'>链</b>的<b class='flag-5'>手动</b>约束

    如何辨别光纤的好坏?

    辨别光纤的好坏,通常涉及一系列测试和检查步骤。以下是一些主要的方法: 光学连通性测试:检查光纤的光学连通性。当输出端测到的光功率与输入端实际输入的光功率的比值小于一定的数值时,
    的头像 发表于 04-11 11:48 1062次阅读

    永久、信道测试的区别

    永久测试和信道测试是网络和通信领域中两个不同的概念,它们通常用于确保网络和通信系统的可靠性和性能。 永久测试(Permanent Link Testing): 永久
    的头像 发表于 03-25 10:59 2557次阅读

    【嵌入式SD NAND】基于FATFS/Littlefs文件系统的日志框架实现

    文章目录【嵌入式】基于FATFS/Littlefs文件系统的日志框架实现1.概述2.设计概要3.设计实现3.1初始化`init`3.2日志写入`write`3.3
    的头像 发表于 03-14 18:12 1159次阅读
    【嵌入式SD NAND】基于FATFS/Littlefs文件系统的<b class='flag-5'>日志</b>框架<b class='flag-5'>实现</b>

    无线控制协议和作用

    无线控制协议是一种用于在无线通信系统中控制数据传输和管理无线的协议。它定义了在无线路上进行通信、调度传输资源、错误处理和适应性调整
    的头像 发表于 02-01 10:51 893次阅读

    Eth-Trunk聚合威廉希尔官方网站 的原理与配置

    随着网络中部署的业务不断增长,对于全双工点对点,单条物理的带宽可能已经不能满足正常业务流量的需求,而且单条
    的头像 发表于 01-02 09:40 1037次阅读
    Eth-Trunk<b class='flag-5'>链</b><b class='flag-5'>路</b>聚合威廉希尔官方网站
的原理与配置