From 1ae91043da3e2fb34edb6ca5596c123eab4dcd37 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 25 Sep 2026 16:47:26 +0200 Subject: [PATCH 1/2] Add failing test for issue 23: MDC and thread name are read on the sender thread Co-Authored-By: Claude Fable 5.1 --- .../logback/LogtailAppenderMdcTest.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/java/com/logtail/logback/LogtailAppenderMdcTest.java diff --git a/src/test/java/com/logtail/logback/LogtailAppenderMdcTest.java b/src/test/java/com/logtail/logback/LogtailAppenderMdcTest.java new file mode 100644 index 0000000..09b2fd8 --- /dev/null +++ b/src/test/java/com/logtail/logback/LogtailAppenderMdcTest.java @@ -0,0 +1,61 @@ +package com.logtail.logback; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import org.slf4j.MDC; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +/** + * Events are queued in append() and serialized later on the sender thread, so everything taken from the logging + * thread (its MDC, its name) has to be captured while the event is still on that thread. + */ +public class LogtailAppenderMdcTest { + + @Test + public void testMdcAndThreadNameAreCapturedWhenTheEventIsQueued() throws Exception { + LoggerContext context = new LoggerContext(); + LogtailAppender appender = new LogtailAppender(); + appender.setContext(context); + appender.setSourceToken("source-token"); + appender.setMdcFields("requestId"); + appender.setMdcTypes("string"); + appender.start(); + Logger logger = context.getLogger("mdc-test"); + logger.addAppender(appender); + + MDC.put("requestId", "req-42"); + try { + logger.info("Queued while the MDC was set"); + } finally { + MDC.remove("requestId"); + } + + AtomicReference json = new AtomicReference<>(); + Thread sender = new Thread(() -> { + try { + json.set(appender.batchToJson(1)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, "logtail-appender"); + sender.start(); + sender.join(); + + Map line = new ObjectMapper().readValue(json.get(), new TypeReference>>() {}).get(0); + Map meta = new HashMap<>(); + meta.put("logger", "mdc-test"); + meta.put("requestId", "req-42"); + assertEquals(meta, line.get("meta")); + assertEquals(Collections.singletonMap("thread", "main"), line.get("runtime")); + } +} From c81de4a1ddac5d1ca255bb3d209201cfa7cf0e8d Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 25 Sep 2026 16:50:24 +0200 Subject: [PATCH 2/2] Capture the MDC and thread name before queueing the event Co-Authored-By: Claude Fable 5.1 --- src/main/java/com/logtail/logback/LogtailAppender.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/logtail/logback/LogtailAppender.java b/src/main/java/com/logtail/logback/LogtailAppender.java index d857e83..8a74585 100644 --- a/src/main/java/com/logtail/logback/LogtailAppender.java +++ b/src/main/java/com/logtail/logback/LogtailAppender.java @@ -101,6 +101,10 @@ protected void append(ILoggingEvent event) { return; } + // The batch is serialized later on another thread: take the formatted message, the thread name and the + // MDC from the logging thread now, as logback's own AsyncAppender does + event.prepareForDeferredProcessing(); + if (batch.size() < maxQueueSize) { batch.add(event); }