From 8fbc270f7ab78eefce93b1cec3df2ef8c569b2df Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 17 Nov 2025 19:56:06 +0100 Subject: [PATCH 1/2] fix map structured args in logstash --- .../v1_0/LogstashStructuredArgsTest.java | 15 ++++++++++++ .../v1_0/internal/LoggingEventMapper.java | 24 +++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/instrumentation/logback/logback-appender-1.0/library/src/logstashStructuredArgsTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/LogstashStructuredArgsTest.java b/instrumentation/logback/logback-appender-1.0/library/src/logstashStructuredArgsTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/LogstashStructuredArgsTest.java index 89359af9f722..5722afe9de44 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/logstashStructuredArgsTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/LogstashStructuredArgsTest.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/logstashStructuredArgsTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/LogstashStructuredArgsTest.java @@ -9,6 +9,8 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.HashMap; +import java.util.Map; import net.logstash.logback.argument.StructuredArguments; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -90,4 +92,17 @@ void structuredArgumentsWithTypedValues() { equalTo(AttributeKey.longKey("timestamp"), timestamp), equalTo(AttributeKey.booleanKey("session_active"), true))); } + + @Test + void logWithStructuredArgument() { + Map eventData = new HashMap<>(); + eventData.put("foo", "bar"); + logger.info("message: {}", StructuredArguments.entries(eventData)); + + testing.waitAndAssertLogRecords( + logRecord -> + logRecord + .hasBody("message: {foo=bar}") + .hasAttributesSatisfying(equalTo(AttributeKey.stringKey("foo"), "bar"))); + } } diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java index fbb74ff559ca..e45cd0975169 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java @@ -639,27 +639,43 @@ private static boolean supportsLogstashStructuredArguments() { @NoMuzzle private void captureLogstashStructuredArguments(LogRecordBuilder builder, Object[] arguments) { for (Object argument : arguments) { - if (isLogstashStructuredArgument(argument)) { - captureLogstashStructuredArgument(builder, argument); + if (isLogstashSingleStructuredArgument(argument)) { + captureLogstashSingleStructuredArgument(builder, argument); + } + if (isLogstashMapStructuredArgument(argument)) { + captureLogstashMapStructuredArgument(builder, argument); } } } @NoMuzzle - private static boolean isLogstashStructuredArgument(Object argument) { + private static boolean isLogstashSingleStructuredArgument(Object argument) { // StructuredArguments implement the marker interface, so we can check for it // without importing the class directly (which may not be available at runtime) return argument instanceof SingleFieldAppendingMarker; } @NoMuzzle - private void captureLogstashStructuredArgument(LogRecordBuilder builder, Object argument) { + private void captureLogstashSingleStructuredArgument(LogRecordBuilder builder, Object argument) { // StructuredArguments created by v() or keyValue() extend SingleFieldAppendingMarker // which has getFieldName() and provides field value via reflection SingleFieldAppendingMarker marker = (SingleFieldAppendingMarker) argument; captureLogstashMarker(builder, marker); } + @NoMuzzle + private static boolean isLogstashMapStructuredArgument(Object argument) { + // StructuredArguments implement the marker interface, so we can check for it + // without importing the class directly (which may not be available at runtime) + return argument instanceof MapEntriesAppendingMarker; + } + + @NoMuzzle + private void captureLogstashMapStructuredArgument(LogRecordBuilder builder, Object argument) { + MapEntriesAppendingMarker marker = (MapEntriesAppendingMarker) argument; + captureLogstashMarker(builder, marker); + } + private interface FieldReader { void read(LogRecordBuilder builder, Object logstashMarker, boolean captureEventName); } From 426fa279dba15e7263d21b1f096baafa54029a95 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 18 Nov 2025 12:17:15 +0100 Subject: [PATCH 2/2] pr review --- .../v1_0/internal/LoggingEventMapper.java | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java index e45cd0975169..a35c72a075ab 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java @@ -639,41 +639,18 @@ private static boolean supportsLogstashStructuredArguments() { @NoMuzzle private void captureLogstashStructuredArguments(LogRecordBuilder builder, Object[] arguments) { for (Object argument : arguments) { - if (isLogstashSingleStructuredArgument(argument)) { - captureLogstashSingleStructuredArgument(builder, argument); - } - if (isLogstashMapStructuredArgument(argument)) { - captureLogstashMapStructuredArgument(builder, argument); + if (isLogstashStructuredArgument(argument)) { + captureLogstashMarker(builder, argument); } } } @NoMuzzle - private static boolean isLogstashSingleStructuredArgument(Object argument) { - // StructuredArguments implement the marker interface, so we can check for it - // without importing the class directly (which may not be available at runtime) - return argument instanceof SingleFieldAppendingMarker; - } - - @NoMuzzle - private void captureLogstashSingleStructuredArgument(LogRecordBuilder builder, Object argument) { - // StructuredArguments created by v() or keyValue() extend SingleFieldAppendingMarker - // which has getFieldName() and provides field value via reflection - SingleFieldAppendingMarker marker = (SingleFieldAppendingMarker) argument; - captureLogstashMarker(builder, marker); - } - - @NoMuzzle - private static boolean isLogstashMapStructuredArgument(Object argument) { + private static boolean isLogstashStructuredArgument(Object argument) { // StructuredArguments implement the marker interface, so we can check for it // without importing the class directly (which may not be available at runtime) - return argument instanceof MapEntriesAppendingMarker; - } - - @NoMuzzle - private void captureLogstashMapStructuredArgument(LogRecordBuilder builder, Object argument) { - MapEntriesAppendingMarker marker = (MapEntriesAppendingMarker) argument; - captureLogstashMarker(builder, marker); + return argument instanceof SingleFieldAppendingMarker + || argument instanceof MapEntriesAppendingMarker; } private interface FieldReader {