【笔记】代码片段记录总结:TraceID 日志链路追踪ID 实现,从 Web 请求到定时任务的全链路覆盖

日志链路追踪 ID 实现

日志链路追踪 ID 实现,从 Web 请求到定时任务的全链路覆盖。

在分布式系统和微服务架构中,日志追踪是排查问题的关键手段。通过统一的追踪 ID(Trace ID),我们可以将分散在不同服务、不同组件中的日志串联起来,形成完整的调用链路。本文将介绍如何在 Java 项目中实现全场景的日志追踪 ID 管理,覆盖 Web 请求、XXL-Job 任务和 Spring 定时任务。

一、核心工具类设计

首先我们需要一个核心工具类来管理追踪 ID,基于 SLF4J 的 MDC(Mapped Diagnostic Context)实现上下文传递:

import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;

/**
 * 日志追踪工具类
 * 封装了MDC中TraceId的创建、设置、获取和清除操作
 */
public class MDCTraceUtils {
    /**
     * 追踪id的名称
     * 建议在日志格式中配置此变量,如:%X{TID}
     */
    public static final String KEY_TRACE_ID = "TID";

    /**
     * filter的优先级,值越低越优先
     */
    public static final int FILTER_ORDER = -1;

    /**
     * 创建traceId并赋值MDC
     * 适用于需要新建追踪ID的场景
     */
    public static void addTraceId() {
        MDC.put(KEY_TRACE_ID, createTraceId());
    }

    /**
     * 赋值MDC(下游dubbo或fegin赋值)
     * 适用于从上游系统传递追踪ID的场景(如服务间调用)
     */
    public static void putTraceId(String traceId) {
        MDC.put(KEY_TRACE_ID, traceId);
    }

    /**
     * 获取MDC中的traceId值
     * 可用于在需要时将追踪ID传递给下游服务
     */
    public static String getTraceId() {
        return MDC.get(KEY_TRACE_ID);
    }

    /**
     * 清除MDC的值
     * 必须在任务结束时调用,避免线程复用导致的上下文污染
     */
    public static void removeTraceId() {
        MDC.remove(KEY_TRACE_ID);
    }

    /**
     * 创建traceId
     */
    public static String createTraceId() {
        return IdUtil.fastSimpleUUID().substring(0, 16).toUpperCase();
    }
}

二、Web 请求链路实现

对于 Web 应用,我们通过过滤器在请求入口处统一处理追踪 ID:

/**
 * Web请求日志链路追踪过滤器
 * 负责在HTTP请求进入时创建或传递TraceId,并在请求结束时清理
 */
@Component
@Order(value = MDCTraceUtils.FILTER_ORDER)
@ConditionalOnClass(value = {HttpServletRequest.class, OncePerRequestFilter.class})
public class WebTraceFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response,
                                    @NotNull FilterChain filterChain) throws IOException, ServletException {
        try {
            // 优先从请求头获取TraceId,实现跨服务追踪
            String traceId = request.getHeader(MDCTraceUtils.KEY_TRACE_ID);
            if (StringUtils.isEmpty(traceId)) {
                // 若请求头中没有,则创建新的TraceId
                MDCTraceUtils.addTraceId();
            } else {
                // 若有则复用,保持链路连贯性
                MDCTraceUtils.putTraceId(traceId);
            }
            // 继续执行过滤器链
            filterChain.doFilter(request, response);
        } finally {
            // 无论请求处理结果如何,最终都要清除TraceId
            // 避免Tomcat线程池复用导致的上下文污染
            MDCTraceUtils.removeTraceId();
        }
    }
}

实现要点

  • 使用OncePerRequestFilter确保每个请求只处理一次
  • 通过@Order设置最高优先级,确保在业务逻辑前执行
  • 支持从请求头获取上游传递的 TraceId,实现分布式追踪
  • 采用 try-finally 结构保证清理操作一定会执行

三、XXL-Job 任务追踪实现

对于 XXL-Job 分布式任务,通过 AOP 实现追踪 ID 管理:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * XXL-Job任务日志链路AOP
 * 为定时任务添加独立的TraceId,便于任务执行日志的追踪
 */
@Aspect
@Component
public class XxlJobTraceAop {

    /**
     * 在XXL-Job任务执行前添加TraceId
     * 切点定义为所有标注@XxlJob注解的方法
     */
    @Before("@annotation(com.xxl.job.core.handler.annotation.XxlJob)")
    public void beforeMethod() {
        // 往当前线程中增加TID
        // 注意:XXL-Job的执行线程由任务调度器管理
        // 实际应用中可根据需要在任务执行完成后手动调用removeTraceId()
        MDCTraceUtils.addTraceId();
    }

}

注意事项

  • XXL-Job 的任务执行线程来自线程池,具有复用性
  • 若任务执行完成后需要清除 TraceId,可添加 @After 注解的方法调用 removeTraceId ()
  • 独立的 TraceId 可以清晰区分不同任务实例的执行日志

四、Spring Scheduled 定时任务追踪

对于 Spring 自带的 @Scheduled 定时任务,同样通过 AOP 实现:

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * Spring定时任务日志链路AOP
 * 为@Scheduled注解的定时任务提供完整的TraceId生命周期管理
 */
@Aspect
@Component
public class ScheduledTraceAop {

    /**
     * 任务执行前创建TraceId
     * 切点精准匹配所有标注@Scheduled注解的方法
     */
    @Before("execution(* *(..)) && @annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void beforeMethod() {
        // 往当前线程中增加TID,不会自动销毁,会保留在当前线程的MDC中,直到显式删除或线程结束
        MDCTraceUtils.addTraceId();
    }

    /**
     * 任务执行后清除TraceId
     * 确保线程池复用不会导致TraceId污染
     */
    @After("@annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void afterMethod() {
        MDCTraceUtils.removeTraceId(); // 显式删除,避免线程池复用带来的脏数据
    }

}

设计考量

  • 采用 @Before 和 @After 注解实现 TraceId 的自动创建与清理
  • 明确的生命周期管理避免了线程池复用带来的上下文污染
  • 精准的切点表达式确保只对定时任务生效

五、Dubbo 服务的 TraceId 传递原理

Dubbo 提供了过滤器(Filter)机制,允许我们在服务调用前后进行自定义处理。通过实现Filter接口,我们可以:

  1. 服务消费者:在发起调用前将当前线程的 TraceId 放入请求上下文
  2. 服务提供者:在接收请求时从上下文提取 TraceId 并设置到本地 MDC
  3. 确保调用完成后清理上下文,避免线程污染

1. Dubbo 消费者过滤器(传递 TraceId)

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Dubbo消费者过滤器:将当前线程的TraceId传递到服务提供者
 */
@Activate(group = CommonConstants.CONSUMER, order = -1000) // 消费者端激活,优先级高于业务过滤器
public class DubboConsumerTraceFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(DubboConsumerTraceFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            // 获取当前线程的TraceId
            String traceId = MDCTraceUtils.getTraceId();
            if (traceId != null) {
                // 将TraceId放入Dubbo调用上下文(attachment)
                RpcContext.getContext().setAttachment(MDCTraceUtils.KEY_TRACE_ID, traceId);
                logger.debug("Dubbo消费者传递TraceId: {}", traceId);
            }
            // 执行远程调用
            return invoker.invoke(invocation);
        } finally {
            // 消费者端无需清除MDC,由上游调用方(如Web过滤器)负责
        }
    }
}

2. Dubbo 提供者过滤器(接收并设置 TraceId)

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Dubbo提供者过滤器:从调用上下文提取TraceId并设置到本地MDC
 */
@Activate(group = CommonConstants.PROVIDER, order = -1000) // 提供者端激活,优先级最高
public class DubboProviderTraceFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(DubboProviderTraceFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            // 从Dubbo调用上下文获取TraceId
            String traceId = RpcContext.getContext().getAttachment(MDCTraceUtils.KEY_TRACE_ID);
            if (traceId != null) {
                // 设置到本地MDC
                MDCTraceUtils.putTraceId(traceId);
                logger.debug("Dubbo提供者接收TraceId: {}", traceId);
            } else {
                // 若没有传递TraceId,则创建新的(适用于直接调用服务的场景)
                MDCTraceUtils.addTraceId();
                logger.debug("Dubbo提供者创建新的TraceId: {}", MDCTraceUtils.getTraceId());
            }
            // 执行服务方法
            return invoker.invoke(invocation);
        } finally {
            // 清除MDC,避免Dubbo线程池复用导致的上下文污染
            MDCTraceUtils.removeTraceId();
        }
    }
}

3. 配置 Dubbo 过滤器

resources/META-INF/dubbo目录下创建org.apache.dubbo.rpc.Filter文件,注册我们的过滤器:

# 格式:过滤器名称=全类名
dubboConsumerTraceFilter=com.yourpackage.filter.DubboConsumerTraceFilter
dubboProviderTraceFilter=com.yourpackage.filter.DubboProviderTraceFilter

4.实现要点说明

  1. 过滤器激活策略
    • 消费者过滤器仅在CONSUMER分组激活
    • 提供者过滤器仅在PROVIDER分组激活
    • 通过order = -1000确保在其他过滤器之前执行,保证 TraceId 尽早设置
  2. 上下文传递机制
    • 利用 Dubbo 的RpcContext作为 TraceId 的传递载体
    • attachment是 Dubbo 专门用于传递附加信息的键值对集合
  3. 异常处理
    • 提供者端使用try-finally确保 TraceId 一定会被清除
    • 消费者端不需要清除 MDC,因为它的生命周期由上游(如 Web 请求过滤器)管理
  4. 兼容性考虑
    • 支持服务提供者被直接调用(无上游 TraceId)的场景,自动创建新 ID
    • 与原有 Web / 定时任务的 TraceId 机制无缝衔接

六、日志配置建议

为了使 TraceId 在日志中生效,需要在日志配置文件中添加 TID 变量,以 logback 为例:

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

总结

本方案通过工具类 + 过滤器 + AOP 的组合方式,实现了以下场景的日志追踪 ID 管理:

  1. Web 请求:从 HTTP 请求进入到响应完成的全链路追踪
  2. XXL-Job 任务:分布式任务的独立追踪 ID
  3. Spring 定时任务:内置定时任务的完整生命周期追踪
  4. Dubbo 服务的 TraceId 传递原理

通过统一的 TraceId,我们可以在日志系统中快速定位和串联相关日志,极大提升问题排查效率。在实际应用中,还可以扩展到 RPC 调用、消息队列等场景,实现全链路追踪。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值