Spring Boot 中使用 MDC 追踪一次请求全过程(日志链路)

Spring Boot 中使用 MDC 追踪一次请求全过程(日志链路)

ControllerLogAspect

package com.yymt.common.trace;import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** @description:* @author: xl* @version: 1.0.0* @date: 2024-05-24 14:22:02*/@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@Aspect
@Slf4j
public class ControllerLogAspect {// 参数类型是下面类型的也不会打印,可以扩展private static List<Class<?>> notLogTypes = Arrays.asList(ServletRequest.class, ServletResponse.class);/*** 拦截所有controller方法** @param joinPoint* @return* @throws Throwable*/@Around("execution(* com.yymt.controller..*(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {long l = System.currentTimeMillis();MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Object result = null;try {// 打印处理当前请求的完整类名和方法log.info("接口方法,{},{}", methodSignature.getDeclaringTypeName(), methodSignature.getName());// 获取所有要打印的参数,丢到map中,key为参数名称,value为参数的值,然后会将这个mp以json的格式输出Map<String, Object> logParamsMap = new LinkedHashMap<>();String[] parameterNames = methodSignature.getParameterNames();Object[] args = joinPoint.getArgs();for (int i = 0; i < args.length; i++) {if (parameterIsLog(methodSignature, i)) {// 参数名称String parameterName = parameterNames[i];// 参数值Object parameterValue = args[i];logParamsMap.put(parameterName, parameterValue);}}log.info("方法参数列表:{}", JSONUtil.toJsonStr(logParamsMap));result = joinPoint.proceed();return result;} finally {if (this.resultIsLog(methodSignature)) {log.info("方法返回值:{}", JSONUtil.toJsonStr(result));}}}/*** 判断参数是否需要打印** @param methodSignature* @param paramIndex* @return*/private boolean parameterIsLog(MethodSignature methodSignature, int paramIndex) {if (methodSignature.getMethod().getParameterCount() == 0) {return false;}// 参数上有 @NoLog注解的不会打印Annotation[] paramAnnotation = methodSignature.getMethod().getParameterAnnotations()[paramIndex];if (paramAnnotation != null && paramAnnotation.length > 0) {for (Annotation annotation : paramAnnotation) {if (annotation.annotationType() == NoLog.class) {return false;}}}// 参数是下面类型的也不会打印Class parameterType = methodSignature.getParameterTypes()[paramIndex];for (Class<?> type : notLogTypes) {if (type.isAssignableFrom(parameterType)) {return false;}}return true;}/*** 判断方法的返回值是否需要打印? 方法上有@NoLog 注解的,表示结果不打印返回值** @param methodSignature* @return*/private boolean resultIsLog(MethodSignature methodSignature) {return methodSignature.getMethod().getAnnotation(NoLog.class) == null;}}

ResultTraceIdAspect

package com.yymt.common.trace;import com.yymt.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @description: 在返回值中填充traceId,用于方便排查错误* @author: xl* @version: 1.0.0* @date: 2024-05-24 14:22:02*/@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@Aspect
@Slf4j
public class ResultTraceIdAspect {@Pointcut("execution(* com.yymt.controller..*(..)) || execution(* com.yymt.common.exception.RRExceptionHandler.*(..))")public void pointCut() {}@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {Object object = joinPoint.proceed();if (object instanceof R) {((R) object).put("traceId", TraceUtils.getTraceId());}return object;}}

TraceFilter

package com.yymt.common.trace;import cn.hutool.core.util.IdUtil;
import com.yymt.common.trace.TraceUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @description:* @author: xl* @version: 1.0.0* @date: 2024-05-24 15:01:46*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter(urlPatterns = "/**", filterName = "TraceFilter")
@Slf4j
public class TraceFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {String traceID = IdUtil.fastSimpleUUID();TraceUtils.setTraceId(traceID);log.info("请求start:{}", request.getRequestURL().toString());long st = System.currentTimeMillis();try {filterChain.doFilter(request, response);} finally {long et = System.currentTimeMillis();log.info("请求end:{},耗时(ms):{}", request.getRequestURL().toString(), (et - st));TraceUtils.removeTraceId();}}}

TraceUtils

package com.yymt.common.trace;import org.slf4j.MDC;/*** @description:* @author: xl* @version: 1.0.0* @date: 2024-05-24 15:33:04*/
public class TraceUtils {public static final String TRACE_ID = "traceId";public static ThreadLocal<String> traceThreadLocal = new ThreadLocal<>();public static String getTraceId() {return traceThreadLocal.get();}public static void setTraceId(String traceId) {traceThreadLocal.set(traceId);MDC.put(TRACE_ID, traceId);}public static void removeTraceId() {traceThreadLocal.remove();MDC.remove(TRACE_ID);}}

NoLog

package com.yymt.common.trace;import java.lang.annotation.*;/*** 不打印日志注解**/
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoLog {}

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><include resource="org/springframework/boot/logging/logback/defaults.xml" /><logger name="org.springframework.web" level="INFO"/><logger name="org.springboot.sample" level="TRACE"/><springProperty scop="context" name="appName" source="spring.application.name" defaultValue="bbt"/><springProperty scop="context" name="rootLevel" source="bbt.logger.level" defaultValue="INFO"/><springProperty scop="context" name="log.path" source="logging.path" defaultValue=""/><springProperty scop="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="local"/><property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} [%thread][%X{traceId}]%logger{50} %3.3L - %msg%n"/><property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){magenta} %highlight(%-5level) %clr([%thread][%X{traceId}]){faint} %clr(%logger{50}){cyan} %clr(%3.3L) %clr(-){faint} %msg%n"/><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><!-- 引用自定义输出模板 --><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern></encoder></appender><appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--记录的日志文件的路径及文件名--><file>${log.path}/debug.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 滚动日志文件保存格式 --><fileNamePattern>${log.path}/%d{yyyy-MM,aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern><MaxFileSize>50MB</MaxFileSize><totalSizeCap>10GB</totalSizeCap><MaxHistory>180</MaxHistory></rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>DEBUG</level></filter><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>${LOG_PATTERN}</pattern></encoder></appender><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--记录的日志文件的路径及文件名--><file>${log.path}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 滚动日志文件保存格式 --><fileNamePattern>${log.path}/%d{yyyy-MM,aux}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern><MaxFileSize>50MB</MaxFileSize><totalSizeCap>10GB</totalSizeCap><MaxHistory>180</MaxHistory></rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>${LOG_PATTERN}</pattern></encoder></appender><appender name="FILE-TOTAL" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--记录的日志文件的路径及文件名--><file>${log.path}/spring.log</file><!--记录的日志级别--><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>DEBUG</level><onMatch>ACCEPT</onMatch><onMismatch>ACCEPT</onMismatch></filter><!--日志文件输出格式--><encoder><pattern>${LOG_PATTERN}</pattern></encoder><!--日志记录器的滚动策略,按日期,按大小记录--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>${log.path}/%d{yyyy-MM,aux}/spring.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern><MaxFileSize>50MB</MaxFileSize><totalSizeCap>10GB</totalSizeCap><MaxHistory>180</MaxHistory></rollingPolicy></appender><root level="INFO"><appender-ref ref="CONSOLE"/>
<!--        <appender-ref ref="STDOUT"/>--><appender-ref ref="DEBUG_FILE"/><appender-ref ref="ERROR_FILE"/><appender-ref ref="FILE-TOTAL"/></root><!-- 开发、测试环境 --><springProfile name="dev,test,local,docker,report-test,test-gc"><logger name="org.springframework.web" level="INFO"/><logger name="org.springboot.sample" level="INFO" /><logger name="com.yymt" level="debug" /></springProfile><!-- 生产环境 : prod-rj表示蓉江新区--><springProfile name="prod,report-prod,prod-fu,prod-rj,prod-jjpcs,prod-hwsq"><logger name="org.springframework.web" level="INFO"/><logger name="org.springboot.sample" level="INFO" /><logger name="com.yymt" level="INFO"/></springProfile></configuration>

多线程使用

注意如果是开启子线程需要自己设置traceId进去,日志才会打印traceId
String traceId = TraceUtils.getTraceId();CompletableFuture.runAsync(() -> {// MDC.put(TRACE_ID, traceId);TraceUtils.setTraceId(traceId);try {log.info("runAsync");} finally {// MDC.clear();TraceUtils.removeTraceId();}}, executor);

参考: Spring Boot 中使用 MDC 追踪一次请求全过程

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

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

相关文章

XShell-连接-Centos 7

XShell 连接Centos 7 一.准备 安装XShell XShell下载地址&#xff1a; 在虚拟机上安装Centos 7&#xff0c;具体操作自行学习 二.Centos 7的准备 1.网络适配器修改为NAT 2.获取IP 输入命令&#xff1a; ip addr我的Centos 7对外IP为192.168.174.129 三.XShell连接Cento…

C++标准模板库(STL)简介

简述 STL&#xff08;Standard Template Library&#xff09;是一套功能强大的 C 模板类和函数的集合&#xff0c;它提供了一系列通用的、可复用的算法和数据结构。它帮住我们实现了基本的数据结构容器&#xff0c;和与之对应的操作。使用它可以减少代码长度&#xff0c;增加代…

TCP 三次握手协议能否变成二次握手

TCP 三次握手协议能否变成二次握手&#xff1f; 看了很多回答&#xff0c;都比较长&#xff0c;按照个人理解的话&#xff0c; 1、第一次握手&#xff1a;建立连接时&#xff0c;客户端发送syn包&#xff08;synj&#xff09;到服务器&#xff0c;并进入SYN_SENT状态&#xf…

docker 离线镜像

1. 在已经部署了镜像的机器上获取镜像   1.1 获取镜像名     docker images   1.2 打包选中对应的镜像     docker save <image_name> -o <image_name>.tar 2. 在将要使用的机器上部署需要的镜像   2.1 加载镜像     docker load -i <image_n…

【百度云千帆AppBuilder】诗词达人:AI引领的诗词文化之旅

文章目录 写在前面&#xff1a;百度云千帆AppBuilder诗词达人&#xff1a;AI引领的诗词文化之旅功能介绍&#xff1a;诗词达人智能体的深度体验1. 诗词接龙学习2. 诗词深度解析3. 互动式问答4. 诗词创作辅助 技术特点详解&#xff1a;"诗词达人"智能体的创新技术零代…

项目9-网页聊天室8(消息的发送和接收之websocket)

这是整个项目最最核心的部分. 但是这个部分的编写&#xff0c;需要依赖"基础设施" 包括不限于前面已经实现的 主界面,用户管理,会话管理, 好友管理, 消息管理 等等.... 发送消息,和接收消息,需要"实时传输 张三 发了一条消息,李四 这边立即就能接收到, 这样的…

【游戏引擎】Unity脚本基础 开启游戏开发之旅

持续更新。。。。。。。。。。。。。。。 【游戏引擎】Unity脚本基础 Unity脚本基础C#语言简介C#基础 Unity脚本基础创建和附加脚本MonoBehaviour生命周期生命周期方法 示例脚本 Unity特有的API常用Unity API 实践示例&#xff1a;制作一个简单的移动脚本步骤1&#xff1a;创建…

对于超长的json数据包,采用分割方式分别上传

例如对于一个长度为5.5M的json数据包&#xff0c;可以将其分割为一包数据长度为64KB大小的小包&#xff0c;再依次读取上传 参考代码如下&#xff1a; #include <stdio.h> #include <string.h>#define SEGMENT_SIZE 64*1024 // 每个数据段的大小&#xff0c;这里…

口碑比较好的相亲交友平台有哪些?正规靠谱的相亲软件排行榜测评

在网络时代&#xff0c;越来越多的人热衷于使用相亲交友软件来寻找生命中的另一半。这些软件确实为许多用户提供了真实可靠的交友平台。然而&#xff0c;市面上的相亲软件种类繁多&#xff0c;质量良莠不齐&#xff0c;让人难以选择。今天&#xff0c;我将介绍几款我使用过且认…

并发编程:ScheduledThreadPoolExecutor你真的了解吗?

前言 首先看到标题&#xff0c;我们其实很多人都知道&#xff0c;但是呢 在实际项目中我们面对很多延迟任务实现方案有很多选择&#xff0c;甚至直接在网上百度反正都能实现就行&#xff0c;但是忽略了很多细节&#xff0c;导致生产上的事故&#xff0c;都是因为没有真正了解到…

Git时光机、Git标签、Git分支、GitHub协作

Git时光机&#xff08;切换版本&#xff09; 1.查看提交历史 HEAD指针指向这次分支的最后一次提交 版本信息一行显示【git log --prettyoneline】 2.引用日志【git reflog】 &#xff08;只在自己的工作区中存在&#xff09; 非常重要&#xff1a;当HEAD指针进行切换之后&…

重学java 43.多线程 多等待多唤醒案例

Fear never builds the future,but hope does. —— 24.5.25 多等待多唤醒问题 在多条线程同时消费同时等待时&#xff0c;会出现问题 BaoZiPu package S77ThreadMoreWait;/*count和flag可以定义成包装类&#xff0c;但要记得给count和flag手动赋值不然对于本案例来说&#xff…

python低阶基础100题(上册)

** python低阶基础100题&#xff08;上册&#xff09; ** 1. 请打印出字符串 Hello World print("Hello World")2. 请打印出字符串 爸爸妈妈&#xff0c;你们辛苦啦 print("爸爸妈妈&#xff0c;你们辛苦啦")3. 请打印出字符串 人生苦短&#xff0c;我…

SVN创建分支,分支合并,切换分支。通俗易懂

1、首先在svnbucket.com远程仓库上创建项目&#xff0c;这里我创建了个测试demo&#xff1a; 2、先把svn仓库的项目检出到自己的文件夹&#xff0c;我这里是demo001文件夹&#xff0c;此时并没有创建truck, branches, tags这三个目录&#xff1a; 3、 在demo001文件夹里新建tru…

vue实战 ---- 社交媒体---黑马头条项目

vue基础 1.介绍 为什么会有Vuex ? ​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 vuex是采用集中式管理组件依赖的共享数据的一个工具&#xff0c;可以解…

spring boot打的包直接运行

Spring Boot 提供了一个插件 spring-boot-maven-plugin 把程序打包成一个可执行的jar包&#xff0c;直接执行java -jar xxx.jar即可以启动程序 1、引用 spring-boot-maven-plugin插件 <build><plugins><plugin><groupId>org.springframework.boot<…

Hive安装教程

前置条件:hadoop&mysql docker容器安装mysql-CSDN博客 以下的/opt/bigdata目录根据自己实际情况更改 1.上传hive包并解压 tar -zxvf apache-hive-3.1.3-bin.tar.gz -C /opt/bigdata/ 2.修改路径 mv /opt/bigdata/apache-hive-3.1.3-bin/ hive cd /opt/bigdata/hive/…

若依框架代码生成器详解:从入门到高级定制

若依框架&#xff08;RuoYi&#xff09;作为一个基于Spring Boot和MyBatis的快速开发平台&#xff0c;提供了强大的代码生成器功能。通过代码生成器&#xff0c;开发者可以迅速创建基础的CRUD&#xff08;增删改查&#xff09;代码&#xff0c;大幅提高开发效率。本文将详细介绍…

电池簇、PCS、电芯之间包含关系

在储能系统中&#xff0c;电池簇&#xff08;Battery Pack&#xff09;、功率转换系统&#xff08;Power Conversion System, PCS&#xff09;、电芯&#xff08;Cell&#xff09;之间存在明确的包含和组成关系。以下是这些组件之间的标准层级关系&#xff1a; 电芯&#xff0…

全网最全网络基础思维导图合集(38张)

计算机网络基础知识点多且杂&#xff0c;想要系统地学习&#xff0c;思维导图肯定是必不可少的。 今天整理了38张思维导图&#xff0c;帮助你轻松理清思路&#xff0c;快速掌握关键内容。建议你收藏起来慢慢看&#xff0c;在看过之后最好能重新动手画一画&#xff0c;让计算机…