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