同一reactor-http-nio线程下Netty HttpClient WRITE事件丢失Sleuth TraceId
Sleuth TraceId在Netty复用线程的WRITE事件中丢失问题解决
问题分析
升级Spring Boot至2.7.6+(当前使用2.7.11)后,复用reactor-http-nio线程时,自定义LoggingHandler处理WRITE事件时丢失Sleuth TraceId,仅首次使用新线程的请求能正常打印TraceId,READ事件无异常。核心原因:
- Netty Channel复用后,绑定在Channel pipeline中的
PayloadLogger会常驻;后续请求的WRITE事件在Netty IO线程执行时,无法从ThreadLocal获取当前请求的Sleuth TraceContext——IO线程复用导致ThreadLocal残留旧上下文或已被清除。 - READ事件正常是因为响应处理流程中,Reactor自动将请求上下文传播到了IO线程,而WRITE事件的触发时机未自动完成上下文传播。
解决方案
修改自定义LoggingHandler,从Reactor请求上下文而非ThreadLocal中获取TraceId,确保每次请求的WRITE事件都能拿到正确的TraceContext。
修改后的代码示例
import reactor.netty.ReactorNetty; import reactor.util.context.Context; import org.springframework.cloud.sleuth.TraceContext; import org.springframework.cloud.sleuth.CurrentTraceContext; import org.springframework.util.StringUtils; import io.netty.buffer.ByteBufHolder; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.logging.LoggingHandler; import reactor.netty.http.client.HttpClient; @Component public class PayloadLogger extends LoggingHandler { private static final String WEBCLIENT_LOGGING_READ_EVENT = "READ"; private static final String WEBCLIENT_LOGGING_WRITE_EVENT = "WRITE"; public PayloadLogger() { super(HttpClient.class, DEBUG, SIMPLE); } @Override protected String format(ChannelHandlerContext ctx, String event, Object arg) { String traceId = getTraceIdFromReactorContext(ctx); if (arg instanceof ByteBufHolder byteBufHolder && StringUtils.equalsAny(event, WEBCLIENT_LOGGING_READ_EVENT, WEBCLIENT_LOGGING_WRITE_EVENT)) { var logMsg = byteBufHolder.content().toString(UTF_8); if (WEBCLIENT_LOGGING_WRITE_EVENT.equals(event)) { return String.format("[TraceId: %s] Request Payload: %s", traceId, logMsg); } if (WEBCLIENT_LOGGING_READ_EVENT.equals(event)) { return String.format("[TraceId: %s] Response Payload: %s", traceId, logMsg); } } return String.format("[TraceId: %s] %s", traceId, super.format(ctx, event, arg)); } private String getTraceIdFromReactorContext(ChannelHandlerContext ctx) { Context reactorContext = ctx.channel().attr(ReactorNetty.REACTOR_CONTEXT).get(); if (reactorContext == null) { return "UNKNOWN"; } TraceContext traceContext = reactorContext.getOrDefault(TraceContext.class, null); if (traceContext != null) { return traceContext.traceId(); } try { traceContext = CurrentTraceContext.get().context(); return traceContext != null ? traceContext.traceId() : "UNKNOWN"; } catch (Exception e) { return "UNKNOWN"; } } }
额外优化建议
- 避免向复用的Connection添加Handler:如果不需要全局复用
PayloadLogger,可改为在每个请求的pipeline中临时添加,确保每次请求绑定当前上下文。示例:
注意:频繁创建Handler实例存在性能损耗,需根据业务场景权衡。HttpClient.create() .doOnRequest((request, connection) -> { connection.addHandlerFirst(new PayloadLogger()); });
验证步骤
- 启动应用,发起多次请求,确保Netty IO线程被复用。
- 检查WRITE事件日志,确认TraceId与当前请求一致。
- 验证READ事件日志的TraceId仍正常输出。
内容的提问来源于stack exchange,提问作者Rayyan
相关产品推荐
相关产品推荐

