SpringBoot项目注入 traceId 来追踪整个请求的日志链路

news2025/3/4 6:27:06

SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排查问题的时候,可以迅速根据 traceId 查找到相关请求的日志,特别是在生产环境的时候,用户可能只提供一个错误截图,我们作为开发人员想要更详细的日志,这时候我们就可以直接根据用户提供的 traceId 来查找用户这次请求的整个日志,会方便很多。

springboot 单体项目没办法做到有 traceId 一样的效果吗?

即使是在单体应用中,为请求添加一个唯一的traceId以追踪整个请求链路的日志也是完全可行的。虽然Spring Cloud Sleuth等工具主要用于分布式系统中的链路追踪,但在单体Spring Boot应用中你仍然可以通过一些方法来实现类似的功能。下面是一个基本的方法来手动实现这样的功能:

手动实现 traceId 注入

1. 创建一个过滤器或拦截器:你可以创建一个Servlet过滤器或者Spring的拦截器,用于在每个请求到达时生成一个唯一的traceId(如果不存在的话),并将其存储到当前线程的上下文中。

2. 使用MDC(Mapped Diagnostic Context):SLF4J提供了MDC工具,可以用来保存日志的上下文信息。你可以在过滤器或拦截器中将traceId放入MDC中,这样所有的日志记录器都可以自动包含这个ID。 示例代码:

TraceIdFilter :

# 我这里用的是 jdk17, 所以 servlet 工具类被移动到了 jakarta 包下,如果你用的是比较老的 jdk, 可以删除后重新导入 servlet 的类。
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.UUID;
@Compone
nt
public class TraceIdFilter extends OncePerRequestFilter {

    private static final String TRACE_ID = "traceId";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String traceId = request.getHeader(TRACE_ID);
        if (traceId == null) {
            traceId = UUID.randomUUID().toString();
        }
        MDC.put(TRACE_ID, traceId);
        try {
            filterChain.doFilter(request, response);
        } finally {
            MDC.remove(TRACE_ID);
        }
    }
}

3. 配置日志格式:确保你的日志配置文件(如logback.xml、log4j2.xml等)中包含了输出traceId的配置。例如,在logback中,你可以这样做:

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - [%thread] %-5level %logger{36} - [%X{traceId}] - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

我的项目使用的是 logback-spring.xml ,我的文件完整内容如下:
logback-spring.xml:

<configuration debug="true">

    <!-- 引用Spring Boot全局配置文件中的日期格式配置项 -->
    <springProperty scope="context" name="dateformat" source="logging.pattern.dateformat" defaultValue="yyyy-MM-dd HH:mm:ss.SSS"/>
    <springProperty scope="context" name="APP_NAME"  source="spring.application.name"/>
    <springProperty scope="context" name="LOG_FILE"  source="logging.file.path"/>
    <!-- 定义颜色编码 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>

    <!-- 定义日志输出到控制台的appender -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%clr(%d{${dateformat}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%15.15t]){faint} [%-40.40logger{39}] [%X{traceId}] %clr(%msg%n){faint}%wEx</pattern>
        </encoder>
    </appender>

    <!-- 开发环境配置 -->
    <springProfile name="local | test">
        <root level="DEBUG">
            <appender-ref ref="STDOUT" />
        </root>

        <!-- 开发环境专属的详细日志 -->
        <logger name="com.tylerzhong" level="TRACE" additivity="false">
            <appender-ref ref="STDOUT" />
        </logger>
    </springProfile>

    <springProfile name="dev | prod">
        <!-- 文件日志:输出全部日志到文件 -->
        <appender name="FILE_INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}/${APP_NAME}.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>ACCEPT</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_FILE}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxHistory>180</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>50MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>%d{${dateformat}} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>

        <!-- 错误日志:用于将错误日志输出到独立文件 -->
        <appender name="FILE_ERROR_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}/error-${APP_NAME}.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_FILE}/error-${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxHistory>180</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>50MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>%d{${dateformat}} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>

        <root level="INFO">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="FILE_INFO_LOG" />
            <appender-ref ref="FILE_ERROR_LOG" />
        </root>

    </springProfile>

</configuration>

关于更具体的日志输出配置,可以参考我的这篇文章《SpringBoot 项目配置日志输出》。

通过这种方式,你就可以在单体Spring Boot项目中为每个请求生成一个唯一的traceId,并通过日志输出它,从而实现在处理请求期间的所有日志条目都能关联起来的效果。这种方法不仅适用于单体应用,也可以作为理解更复杂的分布式追踪概念的基础。

这样配置之后,你在代码中打印日志的时候就会携带上 traceId 了。

 log.info("请求参数: {}", arg);

输出示例:

2025-02-26 19:25:02.187  INFO [nio-8070-exec-2] [com.tylerzhong.web.config.LoggingAspect       ] [ab85097a-6d31-44c3-bcfb-2bcc97b87ab9] 方法: TransferController.xxx(..)

当然,我们不止要将 traceId 输出到控制台,还需要将 traceId 返回给前端用户,这样用户找我们排查问题的时候,只需要给一个 traceId 给我们即可。

所以在返回的包装类中注入 traceId 返回给前端。
我的代码如下:

import lombok.Getter;
import lombok.NoArgsConstructor;
import org.slf4j.MDC;

@Getter
@NoArgsConstructor
public class Result<T> {
    private int code;
    private boolean flag;
    private String desc;
    private String cause;
    private String traceId;
    private T data;
    
    public Result(int code, boolean flag, String desc) {
        this.code = code;
        this.flag = flag;
        this.desc = desc;
    }
    
    public Result(int code, boolean flag, String desc, String traceId) {
        this.code = code;
        this.flag = flag;
        this.desc = desc;
        this.traceId = traceId;
    }

    public Result(int code, boolean flag, String desc, String cause, String traceId) {
        this.code = code;
        this.flag = flag;
        this.desc = desc;
        this.cause = cause;
        this.traceId = traceId;
    }

    public Result(int code, boolean flag, String desc, T data, String traceId) {
        this.code = code;
        this.flag = flag;
        this.desc = desc;
        this.data = data;
        this.traceId = traceId;
    }

    public static <T> Result<T> success() {
        return new Result<>(0, true, "成功", MDC.get("traceId"));
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(0, true,"成功", data, MDC.get("traceId"));
    }

    public static <T> Result<T> fail(int code, String desc, String cause) {
        return new Result<>(code, false, desc, cause, MDC.get("traceId"));
    }
}

返回给前端的失败示例:

{
    "code": 405,
    "flag": false,
    "desc": "请求方式不支持.",
    "cause": "Request method 'POST' is not supported",
    "traceId": "ebd77e99-361c-47b3-8569-2d321d011418",
    "data": null
}

返回给前端的成功示例:

{
    "code": 0,
    "flag": true,
    "desc": "成功",
    "cause": null,
    "traceId": "07a3b99c-8cf8-45dc-a758-6c7636472cab",
    "data": {
        "count": 100,
        "name": "张三"
    }
}

扩展

打印日志内容,你很可能会涉及到 Spring 的 AOP 切面打印日志。可以参考这篇文章《Spring AOP 切面打印日志完整版》

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

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

相关文章

苹果廉价机型 iPhone 16e 影像系统深度解析

【人像拍摄差异】 尽管iPhone 16e支持后期焦点调整功能&#xff0c;但用户无法像iPhone 16系列那样通过点击屏幕实时切换拍摄主体。前置摄像头同样缺失人像深度控制功能&#xff0c;不过TrueTone原彩闪光灯系统在前后摄均有保留。 很多人都高估了 iPhone 的安全性&#xff0c;查…

视觉图像坐标转换

1. 透镜成像 相机的镜头系统将三维场景中的光线聚焦到一个平面&#xff08;即传感器&#xff09;。这个过程可以用小孔成像模型来近似描述&#xff0c;尽管实际相机使用复杂的透镜系统来减少畸变和提高成像质量。 小孔成像模型&#xff1a; 假设有一个理想的小孔&#xff0c;…

脚本无法获取响应主体(原因:CORS Missing Allow Credentials)

背景&#xff1a; 前端的端口号8080&#xff0c;后端8000。需在前端向后端传一个参数&#xff0c;让后端访问数据库去检测此参数是否出现过。涉及跨域请求&#xff0c;一直有这个bug是404文件找不到。 在修改过程当中不小心删除了一段代码&#xff0c;出现了这个bug&#xff0…

leetcode第39题组合总和

原题出于leetcode第39题https://leetcode.cn/problems/combination-sum/description/题目如下&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以…

SQLark 数据迁移|断点续迁已上线(Oracle-达梦)

数据迁移是 SQLark 最受企业和个人用户欢迎的功能之一&#xff0c;截止目前已帮助政府、金融、能源、通信等 50 家单位完成从 Oracle、MySQL 到达梦的全量迁移&#xff0c;自动化迁移成功率达 96% 以上。 在 Oracle 到达梦数据库迁移过程中&#xff0c;SQLark V3.3 新增 断点续…

Element Plus中el-tree点击的节点字体变色加粗

el-tree标签设置 <el-tree class"tree":data"treeData":default-expand-all"true":highlight-current"true"node-click"onTreeNodeClick"><!-- 自定义节点内容&#xff0c;点击的节点字体变色加粗 --><!-- 动…

vmware安装firepower ftd和fmc

在vmware虚拟机中安装cisco firepower下一代防火墙firepower threat defence&#xff08;ftd&#xff09;和管理中心firepower management center&#xff08;fmc&#xff09;。 由于没有cisco官网下载账号&#xff0c;无法下载其中镜像。使用eveng模拟器中的ftd和fmc虚拟镜像…

计算机毕业设计SpringBoot+Vue.js医院资源管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

[含文档+PPT+源码等]精品基于Python实现的微信小程序的乡村医疗咨询系统

基于Python实现的微信小程序的乡村医疗咨询系统背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、社会背景 医疗资源分布不均&#xff1a;在我国&#xff0c;城乡医疗资源分布不均是一个长期存在的问题。乡村地区由于地理位置偏远、经济条件有限&#xff0c;往往…

Python实现GO鹅优化算法优化BP神经网络回归模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 传统BP神经网络的局限性&#xff1a;BP&#xff08;Back Propagation&#xff09;神经网络作为一种…

7.1.2 计算机网络的分类

文章目录 分布范围交换方式 分布范围 计算机网络按照分布范围可分为局域网、广域网、城域网。局域网的范围在10m~1km&#xff0c;例如校园网&#xff0c;网速高&#xff0c;主要用于共享网络资源&#xff0c;拓扑结构简单&#xff0c;约束少。广域网的范围在100km&#xff0c;例…

千峰React:Hooks(上)

什么是Hooks ref引用值 普通变量的改变一般是不好触发函数组件的渲染的&#xff0c;如果想让一般的数据也可以得到状态的保存&#xff0c;可以使用ref import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let num useRef(0)const h…

[Windows] 免费电脑控制手机软件 极限投屏_正式版_3.0.1 (QtScrcpy作者开发)

[Windows] 极限投屏_正式版 链接&#xff1a;https://pan.xunlei.com/s/VOKJf8Z1u5z-cHcTsRpSd89tA1?pwdu5ub# 新增功能(Future)&#xff1a; 支持安卓14(Supports Android 14)提高投屏成功率(Improve the success rate of mirror)加快投屏速度(Accelerate screen mirrorin…

C++初阶—list类

第一章&#xff1a;list的介绍及使用 1.1 list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指…

【子网掩码计算器:Python + Tkinter 实现】

子网掩码计算器&#xff1a;Python Tkinter 实现 引言代码功能概述代码实现思路1. 界面设计2. 功能实现3. 事件处理 子网掩码计算器实现步骤1. 导入必要的库2. 定义主窗口类 SubnetCalculatorApp3. 创建菜单栏4. 创建界面组件5. 判断 IP 地址类别6. 计算子网信息7. 其他功能函…

入门基础项目(SpringBoot+Vue)

文章目录 1. css布局相关2. JS3. Vue 脚手架搭建4. ElementUI4.1 引入ElementUI4.2 首页4.2.1 整体框架4.2.2 Aside-logo4.2.3 Aside-菜单4.2.4 Header-左侧4.2.5 Header-右侧4.2.6 iconfont 自定义图标4.2.7 完整代码 4.3 封装前后端交互工具 axios4.3.1 安装 axios4.3.2 /src…

车载以太网-基于linux的ICMP协议

对于车载以太网-ICMP的技术要求: /** ICMP报文格式解析* -----------------* ICMP协议用于网络诊断和错误报告,常见应用包括Ping测试。* ICMP报文结构包括:IP头部、ICMP头部和ICMP数据部分。* 下面详细介绍每个部分的结构、字段的作用以及如何解析它们。* * ICMP头部结构:*…

虚拟机快照与linux的目录结构

虚拟机快照是对虚拟机某一时刻状态的完整捕获&#xff0c;包括内存、磁盘、配置及虚拟硬件状态等&#xff0c;保存为独立文件。 其作用主要有数据备份恢复、方便系统测试实验、用于灾难恢复以及数据对比分析。具有快速创建和恢复、占用空间小、可多个快照并存的特点。在管理维…

Unity小功能实现:鼠标点击移动物体

1、功能描述 当玩家点击鼠标时&#xff0c;场景中的物体会移动到鼠标点击的位置。这个功能可以用于控制角色移动、放置物体等场景。 2、实现步骤 创建Unity项目&#xff1a;首先&#xff0c;打开Unity并创建一个新的3D项目。 添加3D物体&#xff1a;在场景中创建一个3D物体&am…

算法题笔记(自用)——Python

目录 一. 进制&位运算&ASCAII 二. format格式化输出 1. 基本用法 2. 位置参数 3. 格式化数字 4. 对齐和填充 5. 格式化二进制、八进制、十六进制 6. 格式化百分比 7. 格式化科学计数法 8. 格式化字符串字面量&#xff08;f-string&#xff09; 三. 字符串 使…