SpringBoot中使用TraceId进行日志追踪

**查询日志的痛点:**项目中每当我们查询日志的时候都是看前端请求什么接口,根据一些关键字进入服务器查询日志中是否有这个关键字,然而这个关键字在日志里面并不是唯一的,所以要生成一个唯一的标识,每一次请求都是唯一的一串字符,查询会过滤掉很多无用的信息,快捷查找到这次请求。为了解决这个痛点,就使用了TraceId。

一、TraceId 定义

用于标识某一次具体的请求ID。当用户的请求进入系统后,会在RPC调用网络的第一层生成一个全局唯一的traceId,并且会随着每一层的RPC调用,不断往后传递,这样的话通过traceId就可以把一次用户请求在系统中调用的路径串联起来。

在分布式系统中,一个请求可能会涉及多个服务和组件的调用,而traceId可以帮助我们追踪和查看整个请求的流程和调用链

二、TraceId的请求流程

在最开始请求系统时候生成一个全局唯一的traceId,放在http 请求header中,系统接收到请求后,从header中取出这个traceId,放入MDC中,这个traceId伴随着这整个请求的调用周期,即当一个服务调用另外一个服务的时候,需要将traceId往下传递,从而形成一条链路。

注意:这次的traceId主要是用UUID来生成的,前端生成traceId,后端拦截器拦截到了做一层判断,如果前端有,就不需要后端生成,而且只需要将前端的这个traceId发给后端人员,后端人员就能直接能定位到日志,不需要再让前端给请求的是什么接口,所以建议是前端来生成traceId。

三、SpringBoot整合TraceId

(一)添加一个logback.xml

logback.xml内容如下:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATH" value="/xxx/log"/>  <!-- 项目的日志路径 -->
    <property name="LOG_FILE" value="xxx"/>
    <property name="DATE_PATH" value="${LOG_PATH}/%d{yyyy-MM-dd}"/>

    <!-- 日志输出格式 -->
    <property name="log.pattern.console"
              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) [traceId:%X{traceId}] %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)"/>
    <property name="log.pattern.file"
              value="%d{HH:mm:ss.SSS} [%thread] [traceId:%X{traceId}] %-5level %logger{20} - [%method,%line] - %msg%n"/>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern.console}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <!-- 日志文件fzwyapi-admin.log -->
    <appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${LOG_FILE}.log</file>
        <append>true</append>
        <encoder>
<!--            <pattern>${CONSOLE_LOG_PATTERN}</pattern>-->
            <pattern>${log.pattern.file}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>
                ${DATE_PATH}/%i.log
            </fileNamePattern>
            <maxHistory>15</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
            <maxFileSize>50MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <appender name="fileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/${LOG_FILE}.error.log</file>
        <append>true</append>
        <encoder>
            <!--            <pattern>${CONSOLE_LOG_PATTERN}</pattern>-->
            <pattern>${log.pattern.file}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>
                ${DATE_PATH}/%i.error.log
            </fileNamePattern>
            <maxHistory>15</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
            <maxFileSize>50MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="fileAppender"/>
        <appender-ref ref="fileError"/>
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

(二)添加TraceId工具类

TraceIdUtil.java

复制代码
package xxx;

import org.slf4j.MDC;

import java.util.UUID;

/**
 * @author lyc
 * @date 2024/10/17
 * @funtion
 */
public class TraceIdUtil {
    public static final String TRACE_ID = "traceId";

    public static String getTraceId() {
        String traceId = MDC.get(TRACE_ID);
        return traceId == null ? "" : traceId;
    }

    public static void setTraceId(String traceId) {
        MDC.put(TRACE_ID, traceId);
    }

    public static void remove() {
        MDC.remove(TRACE_ID);
    }

    public static void clear() {
        MDC.clear();
    }

    public static String generateTraceId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

(三)添加TraceId拦截器

SystemTraceIdInterceptor.java

复制代码
package xxx;

import xxx.TraceIdUtil;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author lyc
 * @date 2024/10/17
 * @funtion
 */
@Component
public class SystemTraceIdInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //如果有上层调用就用上层的ID
        String traceId = request.getHeader(TraceIdUtil.TRACE_ID);
        if (traceId == null) {
            traceId = TraceIdUtil.generateTraceId();
        }
        MDC.put(TraceIdUtil.TRACE_ID, traceId);

        //配置traceId到响应header中
        response.setHeader(TraceIdUtil.TRACE_ID, MDC.get(TraceIdUtil.TRACE_ID));

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //调用结束后删除
        MDC.remove(TraceIdUtil.TRACE_ID);
    }
}

(四)InterceptorConfig配置TraceId拦截器

InterceptorConfig.java

复制代码
package xxx.config;

import xxx.SystemTraceIdInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * @author lyc
 * @updated: 2024/10/17
 * @funtion
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Resource
    private SystemTraceIdInterceptor systemTraceIdInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置链路追踪traceId拦截器
        registry.addInterceptor(systemTraceIdInterceptor).addPathPatterns("/**");
    }

}

四、效果

相关推荐
东风破_4 小时前
danci 2:创建的单词书到底存在哪里?从 Supabase 一路理解 ORM、Drizzle 和 RLS
数据库·后端·node.js
东风破_4 小时前
danci 项目(三):从 JSON 数据到 AI Coding,真实项目里的数据清洗、Prompt 和工程规范
前端·后端·node.js
东风破_5 小时前
danci 项目(一):从需求到架构,一个单词学习系统为什么会这样设计
前端·后端·node.js
jay神6 小时前
【计算机毕业设计】基于SpringBoot的程序教学辅助系统
java·前端·vue.js·spring boot·后端·毕业设计·课程设计
2601_962073819 小时前
Spring全面详解(基础版)
java·后端·spring
IT_陈寒9 小时前
Vite打包时的静态资源坑,我帮你踩过了
前端·人工智能·后端
考虑考虑9 小时前
kubectl命令
运维·后端·自动化运维
第五页的你10 小时前
ElasticSearch结合本地消息表实现搜索功能
后端
谢亮_vipxieliang10 小时前
ValidX分组验证详解:不同场景使用不同验证规则
java·spring boot·后端·spring cloud·eclipse·hibernate
超兔一体云10 小时前
MySQL索引优化实战——从慢查询到索引调优
后端·mysql