SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

本文主要是介绍SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链...

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.hphpttp.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</onMismaphptch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    js            <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>
  python              <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配置log4j输出日志的案例讲解》。

通过这种方式,你就可以在单体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.NoArgsCohttp://www.chinasem.cnnstructor;
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": "张三"
    }
}

到此这篇关于SpringBoot项目注入 traceId 来追踪整个请求的日志链路的文章就介绍到这了,更多相关SpringBoot traceId 日志链路内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153571

相关文章

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

SpringBoot3.4配置校验新特性的用法详解

《SpringBoot3.4配置校验新特性的用法详解》SpringBoot3.4对配置校验支持进行了全面升级,这篇文章为大家详细介绍了一下它们的具体使用,文中的示例代码讲解详细,感兴趣的小伙伴可以参考... 目录基本用法示例定义配置类配置 application.yml注入使用嵌套对象与集合元素深度校验开发

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依