From cb4f17d3c367b712f75952c2d2ed5a96dec7a50a Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Wed, 23 Sep 2026 16:28:28 +0200 Subject: [PATCH 1/2] Add failing test for T-3396: the batch is dropped when an event throws from hasCallerData() Co-Authored-By: Claude Fable 5.1 --- .../LogtailAppenderCallerDataTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/test/java/com/logtail/logback/LogtailAppenderCallerDataTest.java diff --git a/src/test/java/com/logtail/logback/LogtailAppenderCallerDataTest.java b/src/test/java/com/logtail/logback/LogtailAppenderCallerDataTest.java new file mode 100644 index 0000000..77064be --- /dev/null +++ b/src/test/java/com/logtail/logback/LogtailAppenderCallerDataTest.java @@ -0,0 +1,44 @@ +package com.logtail.logback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.LoggingEvent; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +/** + * Caller data is optional in the payload, so an event that cannot provide it must not take the whole batch down. + */ +public class LogtailAppenderCallerDataTest { + + @Test + public void testBatchStillSerializesWhenAnEventThrowsFromHasCallerData() throws Exception { + Logger logger = new LoggerContext().getLogger(Logger.ROOT_LOGGER_NAME); + LogtailAppender appender = new LogtailAppender(); + appender.batch.add(new LoggingEvent(Logger.FQCN, logger, Level.INFO, "Regular event", null, new Object[]{})); + appender.batch.add(new LoggingEvent(Logger.FQCN, logger, Level.INFO, "Event whose caller data throws", null, new Object[]{}) { + // The Quarkus logback bridge (io.quarkiverse.logback.runtime.LoggingEventWrapper) rebuilds caller data + // from the JBoss log record inside hasCallerData() and throws when the record carries no source class + @Override + public boolean hasCallerData() { + throw new NullPointerException("Declaring class is null"); + } + }); + + List> lines = new ObjectMapper().readValue(appender.batchToJson(2), new TypeReference>>() {}); + + assertEquals(Arrays.asList("Regular event", "Event whose caller data throws"), + lines.stream().map(line -> line.get("message")).collect(Collectors.toList())); + assertEquals(Collections.singletonMap("thread", "main"), lines.get(1).get("runtime")); + } +} From 4cbe578bd2945f8daf458a668d9c4ecd16283e84 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Wed, 23 Sep 2026 16:30:47 +0200 Subject: [PATCH 2/2] T-3396 Keep sending the batch when an event can't provide caller data Co-Authored-By: Claude Fable 5.1 --- .../com/logtail/logback/LogtailAppender.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/logtail/logback/LogtailAppender.java b/src/main/java/com/logtail/logback/LogtailAppender.java index d857e83..409d212 100644 --- a/src/main/java/com/logtail/logback/LogtailAppender.java +++ b/src/main/java/com/logtail/logback/LogtailAppender.java @@ -309,17 +309,22 @@ protected Map generateLogRuntime(ILoggingEvent event) { Map logRuntime = new HashMap<>(); logRuntime.put("thread", event.getThreadName()); - if (event.hasCallerData()) { - StackTraceElement[] callerData = event.getCallerData(); + try { + if (event.hasCallerData()) { + StackTraceElement[] callerData = event.getCallerData(); - if (callerData.length > 0) { - StackTraceElement callerContext = callerData[0]; + if (callerData.length > 0) { + StackTraceElement callerContext = callerData[0]; - logRuntime.put("class", callerContext.getClassName()); - logRuntime.put("method", callerContext.getMethodName()); - logRuntime.put("file", callerContext.getFileName()); - logRuntime.put("line", callerContext.getLineNumber()); + logRuntime.put("class", callerContext.getClassName()); + logRuntime.put("method", callerContext.getMethodName()); + logRuntime.put("file", callerContext.getFileName()); + logRuntime.put("line", callerContext.getLineNumber()); + } } + } catch (RuntimeException e) { + // Caller data is optional and some ILoggingEvent implementations fail to compute it, e.g. the Quarkus + // logback bridge on a log record without a source class - the line is still worth sending without it } return logRuntime;