Spring Boot 实现日志链路追踪,无需引入组件,让日志定位更方便!

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

点击关注#互联网架构师公众号,领取架构师全套资料 都在这里43c27ebea84c75effcc4f13d0ac6dd64.png

0、2T架构师学习资料干货分

上一篇:ChatGPT研究框架(80页PPT,附下载)

大家好,我是互联网架构师!

前言

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

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

4c5369aa426d0c8cf72a6aa692594722.png

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

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

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

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

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

1e8f69bb42c158b46852ae5c7ade3c6f.png

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

cat -n info.log |grep "a415ad50dbf84e99b1b56a31aacd209c"

或者

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

不多说,开整。

正文

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

6c17f6f6201e60aff1b4af0ae9f7a5e5.png

①pom.xml 依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency><!--lombok配置--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency>
</dependencies>

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--日志存储路径--><property name="log" value="D:/test/log" /><!-- 控制台输出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--输出格式化--><pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- 按天生成日志文件 --><appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件名--><FileNamePattern>${log}/%d{yyyy-MM-dd}.log</FileNamePattern><!--保留天数--><MaxHistory>30</MaxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><!--日志文件最大的大小--><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>10MB</MaxFileSize></triggeringPolicy></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="console" /><appender-ref ref="file" /></root>
</configuration>

application.yml

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

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

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

import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;/*** @Author: JCccc* @Date: 2022-5-30 10:45* @Description:*/
public class LogInterceptor implements HandlerInterceptor {private static final String TRACE_ID = "TRACE_ID";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tid = UUID.randomUUID().toString().replace("-", "");//可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成if (!StringUtils.isEmpty(request.getHeader("TRACE_ID"))){tid=request.getHeader("TRACE_ID");}MDC.put(TRACE_ID, tid);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) {MDC.remove(TRACE_ID);}}

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

WebConfigurerAdapter.java 添加拦截器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @Author: JCccc* @Date: 2022-5-30 10:47* @Description:*/
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {@Beanpublic LogInterceptor logInterceptor() {return new LogInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor());//可以具体制定哪些需要拦截,哪些不拦截,其实也可以使用自定义注解更灵活完成
//                .addPathPatterns("/**")
//                .excludePathPatterns("/testxx.html");}
}

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

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

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

@PostMapping("doTest")
public String doTest(@RequestParam("name") String name) throws InterruptedException {log.info("入参 name={}",name);testTrace();log.info("调用结束 name={}",name);return "Hello,"+name;
}
private void testTrace(){log.info("这是一行info日志");log.error("这是一行error日志");testTrace2();
}
private void testTrace2(){log.info("这也是一行info日志");}

效果(OK的):

112e50d553730d0dd054816147d2f45f.png

还没完。

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

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

7fa0eeb2f317281658c2af2d1708e63e.png

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

9b3e01b5efd5566a2dbc6f3b66295bf0.png

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

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

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

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

import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Callable;
import java.util.concurrent.Future;/*** @Author: JCccc* @Date: 2022-5-30 11:13* @Description:*/
public final class MyThreadPoolTaskExecutor  extends ThreadPoolTaskExecutor  {public MyThreadPoolTaskExecutor() {super();}@Overridepublic void execute(Runnable task) {super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}@Overridepublic Future<?> submit(Runnable task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}
}

③ThreadMdcUtil.java

import org.slf4j.MDC;import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;/*** @Author: JCccc* @Date: 2022-5-30 11:14* @Description:*/
public final class ThreadMdcUtil {private static final String TRACE_ID = "TRACE_ID";// 获取唯一性标识public static String generateTraceId() {return UUID.randomUUID().toString();}public static void setTraceIdIfAbsent() {if (MDC.get(TRACE_ID) == null) {MDC.put(TRACE_ID, generateTraceId());}}/*** 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程** @param callable* @param context* @param <T>* @return*/public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {return callable.call();} finally {MDC.clear();}};}/*** 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程** @param runnable* @param context* @return*/public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}};}
}

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

3fd145bd91caeb0cb67bb178f6df4323.png

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

原文:blog.csdn.net/qq_35387940/article/details/125062368

1、Alibaba开源内网高并发编程手册.pdf

2、2T架构师学习资料干货分享

3、10000+TB 资源,阿里云盘,牛逼!!

4、基本涵盖了Spring所有核心知识点总结

  · END ·

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

f20cb75dab98b588919b40e5c9bffd71.png

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描上方二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连点赞、转发、在看。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chatgpt赋能python:Python如何将PDF转换为Word文档

Python如何将PDF转换为Word文档 在现代电脑使用的日常工作中&#xff0c;PDF格式的文档已经变得很普遍。这种文件格式十分方便&#xff0c;但是有时候可能需要将PDF文件转换为Word文档。幸运的是&#xff0c;Python提供了多种方法来实现这个目标。 为什么要将PDF转换为Word&a…

chatgpt赋能python:Python基础教程:如何利用Python进行地区查询

Python基础教程&#xff1a;如何利用Python进行地区查询 在现代社会&#xff0c;人们越来越关注自己所处的地理位置和周边环境。这就导致了地区查询变得越来越流行&#xff0c;因为它可以让人们更加方便地获取自己想要的信息。 Python作为一门强大的编程语言&#xff0c;不仅…

很多人都去考了计算机二级证书,它真的有用吗?来看看吧

我相信在大学期间,很多人都会选择自考计算机二级的证书,那么就有不少人提出疑问:计算机二级证书真的有用吗? 其实我觉得不应该去凭借一个观点去认为他是否有用,主要还是你掌握了多少知识。我认为大学本身就是一个去不断积累知识的过程,重要的不是你拿的这个证书的结果,…

Tsai库---分享一个好用的时间序列库

分享一个好用的时间序列库 tsai库github地址&#xff1a;https://github.com/timeseriesAI/tsai 使用手册&#xff1a;https://timeseriesai.github.io/tsai/ 描述 tsai是一个先进的时间序列和序列深度学习库。是建立在Pytorch &fastai之上的开源深度学习包&#xff0c;可…

计算机信息处理技术员初级难不难,考信息处理技术员之前,这些问题你要知道!...

信息处理技术员具有计算机与信息处理的基础知识&#xff0c;能根据应用部门的要求&#xff0c;熟练使用计算机有效地、安全地进行信息处理操作&#xff0c;能对个人计算机系统进行日常维护&#xff0c;具有助理工程师(或技术员)的实际工作能力和业务水平。 01信息处理技术员是职…

charles证书过期-mac

手机已安装证书并且信任还是抓不到包&#xff0c;大概率是charles证书过期了 1.重置证书 help > SSL Proxying >Reset Charles Root Certificate... 2.点击Reset 3.在启动台打开「钥匙串访问」 找到对应证书 设置为「始终信任」

计算机考证可以退钱吗?

可以&#xff01;计算机考试在报名期间可以登录报名系统进行退费申请&#xff0c;申请成功后&#xff0c;考试费用将按原支付渠道退回考点&#xff1b;通过集体考点报考的考生&#xff0c;将按原支付渠道退回考点&#xff0c;再由考点退还给考生。由于疫情原因&#xff0c;2020…

中级通信工程师证书会过期吗

通信行业中一般的证书都是有有效期的&#xff0c;比如的华为认证&#xff0c;有效期是3年&#xff0c;到证书持有者在证书超过有效期之前&#xff0c;可以通过再次通过考试或者通过本技术领域的更高级别认证的任意一门考试来延期证书的有效期。那么中级通信工程师证书有有效期吗…

图灵测试是什么?为什么AlphaGo那么牛却过不了?

导读&#xff1a;本文将介绍人工智能的检测手段——图灵测试。 作者&#xff1a;杜振东 涂铭 来源&#xff1a;大数据DT&#xff08;ID&#xff1a;hzdashuju&#xff09; 01 图灵测试相关背景 1946年&#xff0c;冯诺依曼发明了第一台计算机&#xff0c;这被后人称为20世纪最先…

python贪吃蛇游戏源码

python贪吃蛇游戏源码 源码如下&#xff1a; 如果没有安装pygame游戏库&#xff0c;需要安装后才能正常使用 pygame游戏库获取如下&#xff1a; https://pypi.org/project/pygame/ 源码如下&#xff1a; import random import sys import time import pygame from pygame.l…

Android贪吃蛇游戏实现

说明&#xff1a;贪吃蛇游戏是一款比较经典的休闲游戏&#xff0c;这是我做的第一个Android项目&#xff0c;会存在一些问题&#xff0c;文章包括项目的部分源码以及运行界面的一些图片&#xff0c;项目也参考学习了许多大佬的文章&#xff0c;会在文章最后面贴出参考文章&…

VC版的贪吃蛇游戏

学C的时候&#xff0c;课程设计编写了C版的贪吃蛇游戏&#xff0c;当时&#xff0c;将很多的东西都分装在了Snake类中&#xff0c;所以&#xff0c;将其移植到VC中&#xff0c;只需将Snake类稍作修改&#xff0c;然后添加到VC中即可。 此处&#xff0c;我感受到复用性的好处&am…

微信小程序:贪吃蛇

微信小程序&#xff1a;贪吃蛇 wx.js var startX 0; var startY 0; var moveX 0; var moveY 0; //移动位置和开始位置的坐标差值 var X 0; var Y 0; //蛇的对象 var snake {x: 0,y: 0,color: "#ff0000", w: 20,h: 20, } //食物对象 var food {x: 0,y: 0,co…

贪吃蛇的小程序

1 创建项目 1.打开微信开发者工具如图所示的界面&#xff0c;点击“ 2.填写项目以后&#xff0c;点击确定即可。如图所示&#xff1a; 2 编程 1.编写index.wxml的代码如下&#xff1a; <view class"container"><canvas style"width:100%;height:1…

【Python游戏】贪吃蛇升级版——双人贪吃蛇小游戏 | 附带源码

前言 之前做过一个单人版的贪吃蛇&#xff0c;所以常常就是我只能看着别人玩&#xff0c;或者别人看着我玩&#xff0c;却没法两个人一起愉快地玩耍。 由此产生了做一个可以双人玩的贪吃蛇想法&#xff0c;并且从小时候一直到现在&#xff0c;之前python还没有那么火的时候&am…

html版贪吃蛇

<!doctype html><html><head></head><body> <div id"box" style"width:256px;font:25px/25px 宋体;background:#000;color:#9f9;"></div><script> var arr[15]; var next10;//最好判断一下arr[1]!arr[0]x…

贪吃蛇智能版(高级)

说明&#xff1a; 在贪吃蛇智能版&#xff08;中级&#xff09;的基础之上&#xff0c;增加了判断小食物是否安全的方法&#xff0c;并且增加了在寻找食物路径失败和寻找尾巴失败之后&#xff0c;会进行一小段的随机溜达&#xff0c;直到重新找到路径为止&#xff0c;当然这段…

贪吃蛇微信小程序源码

每日分享两个小程序源码 下载地址&#xff1a;无极低码&#xff1a;wheart.cnhttps://wheart.cn/so/home?mdw&ridf6d836c8-d54a-11ed-96fa-52540016e6ac <!--snake.wxml--> <view class"control" bindtouchstart"tapStart" bindtouchmove&qu…

win10系统激活提示无法连接到你组织的激活服务器如何解决

1、在桌面新建一个文本文档&#xff0c;把以下代码复制进去&#xff0c;如图所示&#xff1a; slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX slmgr /skms kms.03k.org slmgr /ato 2、点击文件选择“另存为”&#xff0c;在弹出的界面中&#xff0c;将保存位置选择在桌面&#xff…

无法在此设备上激活WINDOWS因为无法连接到你的组织的激活服务器

在搜索框里搜索 cmd&#xff0c; 使用管理员权限运行&#xff0c;执行一句命令&#xff1a; slmgr /skms http://kms.03k.org点完回车以后&#xff0c;再打一句命令激活 slmgr /ato查激活日期 按下Windows R &#xff0c;输入cmd&#xff0c;出来dos对话框后&#xff0c;输入…