You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Webflux上传Multipart时如何访问MDC中的TraceId

问题:Spring Webflux文件上传接口中MDC TraceId无法访问的解决方法

背景

我基于Spring Boot 3 + Spring Webflux + Micrometer实现链路追踪,相关依赖如下:

implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.micrometer:context-propagation:latest.integration'

已在主方法中添加Hooks.enableAutomaticContextPropagation();来处理MDC和ThreadLocal的上下文传递,普通接口均正常工作,但Multipart文件上传接口无法访问MDC中的TraceId,推测是文件上传的阻塞操作被调度到其他线程,导致上下文未正确传递。

相关代码与现象

日志切面代码

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class LogAop {
    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void controllerPointCut() {
    }

    @Before(value = "controllerPointCut()")
    public void logAdvice(JoinPoint joinPoint) {
        log.info("signature:{}, args:{}", joinPoint.getSignature().toShortString(), joinPoint.getArgs());
    }
}

切面日志输出(无TraceId)

2023-07-19T12:53:29.842+04:30  INFO [                                ,                 ] 19064 --- [or-http-epoll-3] ir.zarebin.profile.aspects.LogAop        : signature:ProfileController.update(..), args:[ab8a8daf-d4f9-4777-b54b-682f0326ceea, ProfileIn[displayName=fuckboy, username=asdssad, image=file:test.jpg], DefaultFilePart{file (test.jpg)}]

切换线程后日志(正常显示TraceId)

2023-07-19T12:53:31.895+04:30  INFO [64b79d810a5369e8ec79c012fa6299a1, ec79c012fa6299a1] 19064 --- [nc-response-1-1] i.z.p.services.impl.CephFileService      : [I198] creating BytBuffer from 1 chunks
2023-07-19T12:53:31.896+04:30  INFO [64b79d810a5369e8ec79c012fa6299a1, ec79c012fa6299a1] 19064 --- [nc-response-1-1] i.z.p.services.impl.CephFileService      : [I208] partData: size=45380

文件上传控制器代码

public Mono<ProfileOut> update(@RequestParam UUID userId,
                               @RequestPart("data") @Valid ProfileIn body,
                               @RequestPart(value = "file", required = false) FilePart image) {
   return Mono.deferContextual(contextView -> {
               ContextSnapshotFactory.builder().contextRegistry(ContextRegistry.getInstance().loadThreadLocalAccessors())
                       .build().captureAll();

                return Mono.just(contextView);
            })
            .flatMap(o -> service.update(userId, body, image));
}

问题

如何在文件上传接口的切面(及其他需要的场景)中正常访问TraceId?


解决方法

1. 问题根源

Spring Webflux处理Multipart请求时,文件解析阶段在Netty的IO线程同步执行,AOP切面的@Before逻辑也在此线程触发,但此时Micrometer的上下文尚未完成从请求到当前线程的传播,导致MDC无TraceId;后续业务逻辑切换到业务线程池后,上下文传播机制生效,因此能正常获取TraceId。

2. 方案一:切面内手动注入Trace信息

修改日志切面,直接通过Tracer获取当前链路信息并放入MDC:

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class LogAop {
    private final Tracer tracer;

    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void controllerPointCut() {
    }

    @Before(value = "controllerPointCut()")
    public void logAdvice(JoinPoint joinPoint) {
        Span span = tracer.currentSpan();
        if (span != null) {
            MDC.put("traceId", span.context().traceId());
            MDC.put("spanId", span.context().spanId());
        }
        try {
            log.info("signature:{}, args:{}", joinPoint.getSignature().toShortString(), joinPoint.getArgs());
        } finally {
            MDC.clear();
        }
    }
}

需注入Tracer(来自micrometer-tracing-bridge-brave依赖),确保能直接获取当前链路的Span信息。

3. 方案二:全局WebFilter提前注入MDC

通过WebFilter在请求进入控制器前完成Trace信息的MDC注入,全局生效:

@Component
public class TraceIdMdcFilter implements WebFilter {
    private final Tracer tracer;

    public TraceIdMdcFilter(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Span span = tracer.currentSpan();
        if (span != null) {
            MDC.put("traceId", span.context().traceId());
            MDC.put("spanId", span.context().spanId());
        }
        return chain.filter(exchange)
                .doFinally(signalType -> MDC.clear());
    }
}

此方案下,所有请求进入控制器前MDC已包含Trace信息,切面执行时自然能正常打印。

4. 修正控制器内的上下文处理逻辑

你当前控制器中ContextSnapshotFactory的用法有误,captureAll()仅捕获上下文但未应用,若需在控制器内手动处理,可改为:

public Mono<ProfileOut> update(@RequestParam UUID userId,
                               @RequestPart("data") @Valid ProfileIn body,
                               @RequestPart(value = "file", required = false) FilePart image) {
    return Mono.deferContextual(contextView -> {
        Span span = contextView.get(Span.class);
        if (span != null) {
            MDC.put("traceId", span.context().traceId());
            MDC.put("spanId", span.context().spanId());
        }
        try {
            return service.update(userId, body, image);
        } finally {
            MDC.clear();
        }
    });
}

该方案仅解决控制器内的MDC问题,切面仍需依赖前两种方案处理。


内容的提问来源于stack exchange,提问作者amir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 13:05:35