Spring WebFlux เป็นเฟรมเวิร์กที่ทำงานแบบ Non-blocking บนพื้นฐานของ Project Reactor ซึ่งทำให้สามารถประมวลผลข้อมูลแบบ Reactive ได้อย่างมีประสิทธิภาพ แต่หนึ่งในปัญหาที่พบได้บ่อยคือ MDC (Mapped Diagnostic Context) ไม่สามารถทำงานได้ตรงตามคาดเหมือนใน Spring MVC ที่ใช้ ThreadLocal
🔍 MDC คืออะไร?
- MDC เป็นวิธีการจัดเก็บข้อมูล context เช่น traceId หรือ userId เพื่อใช้ใน log
- มักใช้ร่วมกับ SLF4J, Logback, หรือ Log4j2
- ใน WebFlux การทำงานแบบ asynchronous ทำให้ Thread เปลี่ยนตลอด และ MDC ที่ใช้ ThreadLocal จึง “หลุด”
💡 ทางออก: ใช้ Reactor Context แทน
Reactor Context คือ object ที่สามารถส่งผ่านข้อมูล context ไปตาม Reactive chain ได้โดยไม่ผูกกับ thread
Mono.deferContextual(ctx -> { String traceId = ctx.get("traceId"); log.info("TRACE = {}", traceId); return Mono.just("ok"); }) .contextWrite(Context.of("traceId", "abc-123"));
🛠️ ใส่ Context ให้กับ Service
public Mono<String> handleRequest() { String traceId = UUID.randomUUID().toString(); return service.doWork() .contextWrite(Context.of("traceId", traceId)); }
public Mono<Void> doWork() { return Mono.deferContextual(ctx -> { String traceId = ctx.getOrDefault("traceId", "N/A"); log.info("Doing work for traceId = {}", traceId); return Mono.empty(); }); }
⚠️ ปัญหา MDC แบบเก่า
Mono.just("start") .doOnNext(v -> log.info("traceId = {}", MDC.get("traceId")));
ผลลัพธ์: traceId จะเป็น null หรือ “หายไป” เพราะไม่มีการ bind MDC ให้ทุก thread ใหม่
🔁 วิธีเชื่อม MDC กับ Reactor Context
Hooks.onEachOperator("mdc", Operators.lift((sc, sub) -> new CoreSubscriber<>() { @Override public void onSubscribe(Subscription s) { sub.onSubscribe(s); } @Override public void onNext(Object t) { Context context = sub.currentContext(); String traceId = context.getOrDefault("traceId", "N/A"); try (MDC.MDCCloseable c = MDC.putCloseable("traceId", traceId)) { sub.onNext(t); } } @Override public void onError(Throwable t) { sub.onError(t); } @Override public void onComplete() { sub.onComplete(); } @Override public Context currentContext() { return sub.currentContext(); } } ));
📄 ตัวอย่าง Logback Pattern
<pattern>[%X{traceId}] %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
📦 Spring Cloud Sleuth (Deprecated)
Spring Cloud Sleuth เคยช่วยให้เราสามารถใช้ MDC ได้ง่ายในระบบ distributed แต่ตอนนี้แนะนำให้ใช้ Micrometer Tracing แทนใน Spring Boot 3+
📸 ภาพประกอบ Context Propagation

✅ สรุป
- WebFlux ต้องใช้ Context ที่ไม่พึ่งพา ThreadLocal
- ควรใช้
contextWrite()
และdeferContextual()
แทนการใช้ MDC โดยตรง - ถ้าจำเป็นต้องใช้ MDC ให้ผสานกับ context ผ่าน hook
- แนะนำใช้ Micrometer Tracing + Brave/OTel หากต้องการ distributed trace
🔍 คำค้น SEO
context propagation mdc, spring mdc, context passing webflux, reactor context, reactor deferContextual, spring log traceId, webflux logging, reactive logging traceId, micrometer tracing spring boot