From 79c9677aa3b91f767df4b1f355aa29fc1e4f1cb3 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sun, 4 May 2025 11:16:43 +0200 Subject: [PATCH 01/13] wip --- .../library/build.gradle.kts | 17 +++ .../v2_17/internal/LogEventMapper.java | 139 ++++++++++++++---- .../v2_17/internal/LogEventMapperTest.java | 33 +++-- 3 files changed, 147 insertions(+), 42 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts b/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts index aabb6ce4a876..636a097d317a 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts +++ b/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts @@ -7,6 +7,8 @@ dependencies { annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.0") implementation(project(":instrumentation:log4j:log4j-context-data:log4j-context-data-2.17:library-autoconfigure")) + implementation("io.opentelemetry:opentelemetry-api-incubator:1.50.0-SNAPSHOT") + implementation("io.opentelemetry:opentelemetry-sdk-logs:1.50.0-SNAPSHOT") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testLibrary("com.lmax:disruptor:3.3.4") @@ -17,6 +19,17 @@ dependencies { } } +repositories { + mavenLocal() + mavenCentral() +} + +configurations.all { + resolutionStrategy { + force("io.opentelemetry:opentelemetry-sdk:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-sdk-logs:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-sdk-common:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-api:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-api-incubator:1.50.0-alpha-SNAPSHOT") + } +} + tasks { withType().configureEach { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) @@ -31,3 +44,7 @@ tasks { dependsOn(testAsyncLogger) } } + +tasks.test { + systemProperty("log4j2.threadContextMap", "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap") +} diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index e26958f5215b..a3546fd7464b 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -6,8 +6,10 @@ package io.opentelemetry.instrumentation.log4j.appender.v2_17.internal; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; +import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.context.Context; @@ -15,12 +17,17 @@ import io.opentelemetry.semconv.ExceptionAttributes; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; @@ -98,7 +105,7 @@ public void mapLogEvent( Supplier sourceSupplier, Context context) { - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); captureMessage(builder, attributes, message); @@ -134,57 +141,137 @@ public void mapLogEvent( } attributes.put(CODE_NAMESPACE, source.getClassName()); attributes.put(CODE_FUNCTION, source.getMethodName()); - int lineNumber = source.getLineNumber(); + long lineNumber = source.getLineNumber(); if (lineNumber > 0) { attributes.put(CODE_LINENO, lineNumber); } } } - builder.setAllAttributes(attributes.build()); + if (builder instanceof ExtendedLogRecordBuilder) { + ((ExtendedLogRecordBuilder) builder).setAllAttributes(attributes.build()); + } else { + builder.setAllAttributes(attributes.build().asAttributes()); + } builder.setContext(context); } // visible for testing - void captureMessage(LogRecordBuilder builder, AttributesBuilder attributes, Message message) { + void captureMessage( + LogRecordBuilder builder, ExtendedAttributesBuilder attributes, Message message) { if (message == null) { return; } - if (!(message instanceof MapMessage)) { - builder.setBody(message.getFormattedMessage()); + + if (message instanceof MutableLogEvent) { + captureLogEvent(attributes, (MutableLogEvent) message); + return; + } + + if (message instanceof MapMessage) { + captureMapMessage(builder, attributes, (MapMessage) message); return; } - MapMessage mapMessage = (MapMessage) message; + builder.setBody(message.getFormattedMessage()); + } + @SuppressWarnings("unchecked") + private void captureMapMessage( + LogRecordBuilder builder, ExtendedAttributesBuilder attributes, MapMessage mapMessage) { String body = mapMessage.getFormat(); - boolean checkSpecialMapMessageAttribute = (body == null || body.isEmpty()); - if (checkSpecialMapMessageAttribute) { + boolean checkSpecialMapMessageAttribute = false; + if (body == null || body.isEmpty()) { + checkSpecialMapMessageAttribute = true; body = mapMessage.get(SPECIAL_MAP_MESSAGE_ATTRIBUTE); } - if (body != null && !body.isEmpty()) { builder.setBody(body); } if (captureMapMessageAttributes) { - // TODO (trask) this could be optimized in 2.9 and later by calling MapMessage.forEach() - mapMessage - .getData() - .forEach( - (key, value) -> { - if (value != null - && (!checkSpecialMapMessageAttribute - || !key.equals(SPECIAL_MAP_MESSAGE_ATTRIBUTE))) { - attributes.put(getMapMessageAttributeKey(key), value.toString()); - } - }); + consumeAttributes(mapMessage.getData()::forEach, attributes, checkSpecialMapMessageAttribute); } } - // visible for testing - void captureContextDataAttributes(AttributesBuilder attributes, T contextData) { + @SuppressWarnings("unchecked") + private void captureLogEvent(ExtendedAttributesBuilder attributes, MutableLogEvent logEvent) { + if (captureMapMessageAttributes) { + consumeAttributes( + biConsumer -> + logEvent.getContextData().forEach((s, object) -> biConsumer.accept(s, object)), + attributes, + false); + } + } + @SuppressWarnings("rawtypes") + private static void consumeAttributes( + Consumer actionConsumer, + ExtendedAttributesBuilder attributes, + boolean checkSpecialMapMessageAttribute) { + actionConsumer.accept( + (key, value) -> { + if (value != null + && (!checkSpecialMapMessageAttribute || !key.equals(SPECIAL_MAP_MESSAGE_ATTRIBUTE))) { + consumeEntry(key.toString(), value, attributes); + } + }); + } + + @SuppressWarnings({"unchecked"}) + private static void consumeEntry(String key, Object value, ExtendedAttributesBuilder attributes) { + if (value instanceof String) { + attributes.put(key, (String) value); + } else if (value instanceof Boolean) { + attributes.put(key, (Boolean) value); + } else if ((value instanceof Long) || (value instanceof Integer)) { + attributes.put(key, (long) value); + } else if ((value instanceof Double) || (value instanceof Float)) { + attributes.put(key, (double) value); + } else if (value instanceof List) { + List list = (List) value; + if (list.isEmpty()) { + return; + } + + Object first = list.get(0); + if (first instanceof String) { + attributes.put(ExtendedAttributeKey.stringArrayKey(key), (List) value); + } else if (first instanceof Boolean) { + attributes.put(ExtendedAttributeKey.booleanArrayKey(key), (List) value); + } else if ((first instanceof Long) || (first instanceof Integer)) { + attributes.put(ExtendedAttributeKey.longArrayKey(key), (List) value); + } else if ((first instanceof Double) || (first instanceof Float)) { + attributes.put(ExtendedAttributeKey.doubleArrayKey(key), (List) value); + } + } else if (value instanceof String[]) { + attributes.put(key, (String[]) value); + } else if (value instanceof boolean[]) { + attributes.put(key, (boolean[]) value); + } else if (value instanceof long[]) { + attributes.put(key, (long[]) value); + } else if (value instanceof int[]) { + attributes.put(key, Arrays.stream((int[]) value).asLongStream().toArray()); + } else if (value instanceof double[]) { + attributes.put(key, (double[]) value); + } else if (value instanceof float[]) { + double[] arr = new double[((float[]) value).length]; + for (int i = 0; i < arr.length; ++i) { + arr[i] = ((float[]) value)[i]; + } + attributes.put(key, arr); + } else if ((value instanceof Map)) { + ExtendedAttributesBuilder nestedAttribute = ExtendedAttributes.builder(); + consumeAttributes(((Map) value)::forEach, nestedAttribute, false); + attributes.put(ExtendedAttributeKey.extendedAttributesKey(key), nestedAttribute.build()); + } else { + throw new IllegalArgumentException("Unrecognized value type: " + value.getClass()); + } + } + + // visible for testing + void captureContextDataAttributes(ExtendedAttributesBuilder attributes, T contextData) { if (captureAllContextDataAttributes) { contextDataAccessor.forEach( contextData, @@ -213,7 +300,7 @@ public static AttributeKey getMapMessageAttributeKey(String key) { key, k -> AttributeKey.stringKey("log4j.map_message." + k)); } - private static void setThrowable(AttributesBuilder attributes, Throwable throwable) { + private static void setThrowable(ExtendedAttributesBuilder attributes, Throwable throwable) { // TODO (trask) extract method for recording exception into // io.opentelemetry:opentelemetry-api attributes.put(ExceptionAttributes.EXCEPTION_TYPE, throwable.getClass().getName()); diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java index ce374ff97b9b..e3ccaa513dd8 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java @@ -14,8 +14,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; import java.util.HashMap; import java.util.Map; @@ -36,13 +36,13 @@ void testDefault() { Map contextData = new HashMap<>(); contextData.put("key1", "value1"); contextData.put("key2", "value2"); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureContextDataAttributes(attributes, contextData); // then - assertThat(attributes.build()).isEmpty(); + assertThat(attributes.build().asAttributes()).isEmpty(); } @Test @@ -54,13 +54,13 @@ void testSome() { Map contextData = new HashMap<>(); contextData.put("key1", "value1"); contextData.put("key2", "value2"); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureContextDataAttributes(attributes, contextData); // then - assertThat(attributes.build()).containsOnly(attributeEntry("key2", "value2")); + assertThat(attributes.build().asAttributes()).containsOnly(attributeEntry("key2", "value2")); } @Test @@ -72,13 +72,13 @@ void testAll() { Map contextData = new HashMap<>(); contextData.put("key1", "value1"); contextData.put("key2", "value2"); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureContextDataAttributes(attributes, contextData); // then - assertThat(attributes.build()) + assertThat(attributes.build().asAttributes()) .containsOnly(attributeEntry("key1", "value1"), attributeEntry("key2", "value2")); } @@ -94,14 +94,14 @@ void testCaptureMapMessageDisabled() { message.put("message", "value2"); LogRecordBuilder logRecordBuilder = mock(LogRecordBuilder.class); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureMessage(logRecordBuilder, attributes, message); // then verify(logRecordBuilder).setBody("value2"); - assertThat(attributes.build()).isEmpty(); + assertThat(attributes.build().asAttributes()).isEmpty(); } @Test @@ -116,14 +116,15 @@ void testCaptureMapMessageWithSpecialAttribute() { message.put("message", "value2"); LogRecordBuilder logRecordBuilder = mock(LogRecordBuilder.class); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureMessage(logRecordBuilder, attributes, message); // then verify(logRecordBuilder).setBody("value2"); - assertThat(attributes.build()).containsOnly(attributeEntry("log4j.map_message.key1", "value1")); + assertThat(attributes.build().asAttributes()) + .containsOnly(attributeEntry("log4j.map_message.key1", "value1")); } @Test @@ -138,14 +139,14 @@ void testCaptureMapMessageWithoutSpecialAttribute() { message.put("key2", "value2"); LogRecordBuilder logRecordBuilder = mock(LogRecordBuilder.class); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureMessage(logRecordBuilder, attributes, message); // then verify(logRecordBuilder, never()).setBody(anyString()); - assertThat(attributes.build()) + assertThat(attributes.build().asAttributes()) .containsOnly( attributeEntry("log4j.map_message.key1", "value1"), attributeEntry("log4j.map_message.key2", "value2")); @@ -163,14 +164,14 @@ void testCaptureStructuredDataMessage() { message.put("message", "value2"); LogRecordBuilder logRecordBuilder = mock(LogRecordBuilder.class); - AttributesBuilder attributes = Attributes.builder(); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when mapper.captureMessage(logRecordBuilder, attributes, message); // then verify(logRecordBuilder).setBody("a message"); - assertThat(attributes.build()) + assertThat(attributes.build().asAttributes()) .containsOnly( attributeEntry("log4j.map_message.key1", "value1"), attributeEntry("log4j.map_message.message", "value2")); From dbbdbf222e0c0bf0d721e29dae993d52fb2e281c Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sun, 4 May 2025 11:41:09 +0200 Subject: [PATCH 02/13] cache --- .../v2_17/internal/LogEventMapper.java | 79 +++++++++++++------ .../v2_17/OpenTelemetryAppenderTest.java | 5 ++ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index a3546fd7464b..2df48d7fa07f 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -7,8 +7,10 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributeType; import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; +import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl; import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Severity; @@ -49,9 +51,9 @@ public final class LogEventMapper { private static final String SPECIAL_MAP_MESSAGE_ATTRIBUTE = "message"; - private static final Cache> contextDataAttributeKeyCache = + private static final Cache> contextDataAttributeKeyCache = Cache.bounded(100); - private static final Cache> mapMessageAttributeKeyCache = + private static final Cache> mapMessageAttributeKeyCache = Cache.bounded(100); private static final AttributeKey LOG_MARKER = AttributeKey.stringKey("log4j.marker"); @@ -222,13 +224,14 @@ private static void consumeAttributes( @SuppressWarnings({"unchecked"}) private static void consumeEntry(String key, Object value, ExtendedAttributesBuilder attributes) { if (value instanceof String) { - attributes.put(key, (String) value); + attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.STRING), (String) value); } else if (value instanceof Boolean) { - attributes.put(key, (Boolean) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.BOOLEAN), (Boolean) value); } else if ((value instanceof Long) || (value instanceof Integer)) { - attributes.put(key, (long) value); + attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.LONG), (long) value); } else if ((value instanceof Double) || (value instanceof Float)) { - attributes.put(key, (double) value); + attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE), (double) value); } else if (value instanceof List) { List list = (List) value; if (list.isEmpty()) { @@ -237,34 +240,49 @@ private static void consumeEntry(String key, Object value, ExtendedAttributesBui Object first = list.get(0); if (first instanceof String) { - attributes.put(ExtendedAttributeKey.stringArrayKey(key), (List) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.STRING_ARRAY), + (List) value); } else if (first instanceof Boolean) { - attributes.put(ExtendedAttributeKey.booleanArrayKey(key), (List) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.BOOLEAN_ARRAY), + (List) value); } else if ((first instanceof Long) || (first instanceof Integer)) { - attributes.put(ExtendedAttributeKey.longArrayKey(key), (List) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.LONG_ARRAY), (List) value); } else if ((first instanceof Double) || (first instanceof Float)) { - attributes.put(ExtendedAttributeKey.doubleArrayKey(key), (List) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE_ARRAY), + (List) value); } } else if (value instanceof String[]) { - attributes.put(key, (String[]) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.STRING_ARRAY), (String[]) value); } else if (value instanceof boolean[]) { - attributes.put(key, (boolean[]) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.BOOLEAN_ARRAY), (boolean[]) value); } else if (value instanceof long[]) { - attributes.put(key, (long[]) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.LONG_ARRAY), (long[]) value); } else if (value instanceof int[]) { - attributes.put(key, Arrays.stream((int[]) value).asLongStream().toArray()); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.LONG_ARRAY), + Arrays.stream((int[]) value).asLongStream().toArray()); } else if (value instanceof double[]) { - attributes.put(key, (double[]) value); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE_ARRAY), (double[]) value); } else if (value instanceof float[]) { double[] arr = new double[((float[]) value).length]; for (int i = 0; i < arr.length; ++i) { arr[i] = ((float[]) value)[i]; } - attributes.put(key, arr); + attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE_ARRAY), arr); } else if ((value instanceof Map)) { ExtendedAttributesBuilder nestedAttribute = ExtendedAttributes.builder(); consumeAttributes(((Map) value)::forEach, nestedAttribute, false); - attributes.put(ExtendedAttributeKey.extendedAttributesKey(key), nestedAttribute.build()); + attributes.put( + getMapMessageAttributeKey(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES), + nestedAttribute.build()); } else { throw new IllegalArgumentException("Unrecognized value type: " + value.getClass()); } @@ -277,7 +295,7 @@ void captureContextDataAttributes(ExtendedAttributesBuilder attributes, T contex contextData, (key, value) -> { if (value != null) { - attributes.put(getContextDataAttributeKey(key), value); + attributes.put(getContextDataAttributeKey(key, ExtendedAttributeType.STRING), value); } }); return; @@ -286,18 +304,31 @@ void captureContextDataAttributes(ExtendedAttributesBuilder attributes, T contex for (String key : captureContextDataAttributes) { String value = contextDataAccessor.getValue(contextData, key); if (value != null) { - attributes.put(getContextDataAttributeKey(key), value); + attributes.put(getContextDataAttributeKey(key, ExtendedAttributeType.STRING), value); } } } - public static AttributeKey getContextDataAttributeKey(String key) { - return contextDataAttributeKeyCache.computeIfAbsent(key, AttributeKey::stringKey); + private static ExtendedAttributeKey getContextDataAttributeKey( + String key, ExtendedAttributeType type) { + return getAttributeKey(key, type, contextDataAttributeKeyCache); } - public static AttributeKey getMapMessageAttributeKey(String key) { - return mapMessageAttributeKeyCache.computeIfAbsent( - key, k -> AttributeKey.stringKey("log4j.map_message." + k)); + private static ExtendedAttributeKey getMapMessageAttributeKey( + String key, ExtendedAttributeType type) { + return getAttributeKey("log4j.map_message." + key, type, mapMessageAttributeKeyCache); + } + + @SuppressWarnings("unchecked") + private static ExtendedAttributeKey getAttributeKey( + String key, ExtendedAttributeType type, Cache> cache) { + ExtendedAttributeKey output = + cache.computeIfAbsent(key, k -> InternalExtendedAttributeKeyImpl.create(key, type)); + if (output.getType() != type) { + output = InternalExtendedAttributeKeyImpl.create(key, type); + cache.put(key, output); + } + return (ExtendedAttributeKey) output; } private static void setThrowable(ExtendedAttributesBuilder attributes, Throwable throwable) { diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java index b20cae81297a..3b713dcb1e67 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java @@ -11,6 +11,8 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.spi.ObjectThreadContextMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -41,6 +43,9 @@ void logWithSpan() { // Does not work for log replay, but it is not likely to oc testing.runWithSpan( "span1", () -> { + ObjectThreadContextMap map = + (ObjectThreadContextMap) ThreadContext.getThreadContextMap(); + map.putValue("stuff", new int[] {1, 2, 3}); logger.info("log message"); return Span.current(); }); From 2c2f9478138e7691f3c1b12aee5863abe4b3df1a Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sun, 4 May 2025 13:00:46 +0200 Subject: [PATCH 03/13] more stuff --- .../log4j/appender/v2_17/Log4jHelper.java | 8 +- .../appender/v2_17/OpenTelemetryAppender.java | 16 +- .../v2_17/internal/ContextDataAccessor.java | 14 +- .../v2_17/internal/LogEventMapper.java | 145 ++++++++++-------- .../v2_17/internal/LogEventMapperTest.java | 26 ++-- 5 files changed, 122 insertions(+), 87 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java index 6fc308cc1dd9..ca2b49ce1695 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java @@ -30,7 +30,7 @@ public final class Log4jHelper { - private static final LogEventMapper> mapper; + private static final LogEventMapper> mapper; private static final boolean captureExperimentalAttributes; private static final MethodHandle stackTraceMethodHandle = getStackTraceMethodHandle(); @@ -147,17 +147,17 @@ private static MethodHandle getStackTraceMethodHandle() { } } - private enum ContextDataAccessorImpl implements ContextDataAccessor> { + private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { INSTANCE; @Override @Nullable - public String getValue(Map contextData, String key) { + public Object getValue(Map contextData, String key) { return contextData.get(key); } @Override - public void forEach(Map contextData, BiConsumer action) { + public void forEach(Map contextData, BiConsumer action) { contextData.forEach(action); } } diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java index ff980b3be35a..0d1ebcee682a 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java @@ -300,11 +300,13 @@ private void emit(OpenTelemetry openTelemetry, LogEvent event) { // when using async logger we'll be executing on a different thread than what started logging // reconstruct the context from context data if (context == Context.root()) { - ContextDataAccessor contextDataAccessor = ContextDataAccessorImpl.INSTANCE; - String traceId = contextDataAccessor.getValue(contextData, ContextDataKeys.TRACE_ID_KEY); - String spanId = contextDataAccessor.getValue(contextData, ContextDataKeys.SPAN_ID_KEY); + ContextDataAccessor contextDataAccessor = + ContextDataAccessorImpl.INSTANCE; + String traceId = + contextDataAccessor.getStringValue(contextData, ContextDataKeys.TRACE_ID_KEY); + String spanId = contextDataAccessor.getStringValue(contextData, ContextDataKeys.SPAN_ID_KEY); String traceFlags = - contextDataAccessor.getValue(contextData, ContextDataKeys.TRACE_FLAGS_KEY); + contextDataAccessor.getStringValue(contextData, ContextDataKeys.TRACE_FLAGS_KEY); if (traceId != null && spanId != null && traceFlags != null) { context = Context.root() @@ -340,17 +342,17 @@ private void emit(OpenTelemetry openTelemetry, LogEvent event) { builder.emit(); } - private enum ContextDataAccessorImpl implements ContextDataAccessor { + private enum ContextDataAccessorImpl implements ContextDataAccessor { INSTANCE; @Override @Nullable - public String getValue(ReadOnlyStringMap contextData, String key) { + public Object getValue(ReadOnlyStringMap contextData, String key) { return contextData.getValue(key); } @Override - public void forEach(ReadOnlyStringMap contextData, BiConsumer action) { + public void forEach(ReadOnlyStringMap contextData, BiConsumer action) { contextData.forEach(action::accept); } } diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/ContextDataAccessor.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/ContextDataAccessor.java index e1552abe8045..65172cf149e1 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/ContextDataAccessor.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/ContextDataAccessor.java @@ -12,10 +12,18 @@ * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ -public interface ContextDataAccessor { +public interface ContextDataAccessor { @Nullable - String getValue(T contextData, String key); + V getValue(T contextData, String key); - void forEach(T contextData, BiConsumer action); + default String getStringValue(T contextData, String key) { + Object value = getValue(contextData, key); + if (value != null) { + return value.toString(); + } + return null; + } + + void forEach(T contextData, BiConsumer action); } diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index 2df48d7fa07f..85d30ea9b271 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -19,17 +19,19 @@ import io.opentelemetry.semconv.ExceptionAttributes; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; @@ -58,7 +60,7 @@ public final class LogEventMapper { private static final AttributeKey LOG_MARKER = AttributeKey.stringKey("log4j.marker"); - private final ContextDataAccessor contextDataAccessor; + private final ContextDataAccessor contextDataAccessor; private final boolean captureExperimentalAttributes; private final boolean captureCodeAttributes; @@ -68,7 +70,7 @@ public final class LogEventMapper { private final boolean captureAllContextDataAttributes; public LogEventMapper( - ContextDataAccessor contextDataAccessor, + ContextDataAccessor contextDataAccessor, boolean captureExperimentalAttributes, boolean captureCodeAttributes, boolean captureMapMessageAttributes, @@ -159,51 +161,36 @@ public void mapLogEvent( } // visible for testing + @SuppressWarnings("unchecked") void captureMessage( LogRecordBuilder builder, ExtendedAttributesBuilder attributes, Message message) { if (message == null) { return; } - - if (message instanceof MutableLogEvent) { - captureLogEvent(attributes, (MutableLogEvent) message); - return; - } - - if (message instanceof MapMessage) { - captureMapMessage(builder, attributes, (MapMessage) message); + if (!(message instanceof MapMessage)) { + builder.setBody(message.getFormattedMessage()); return; } - builder.setBody(message.getFormattedMessage()); - } + MapMessage mapMessage = (MapMessage) message; - @SuppressWarnings("unchecked") - private void captureMapMessage( - LogRecordBuilder builder, ExtendedAttributesBuilder attributes, MapMessage mapMessage) { String body = mapMessage.getFormat(); - boolean checkSpecialMapMessageAttribute = false; - if (body == null || body.isEmpty()) { - checkSpecialMapMessageAttribute = true; + boolean checkSpecialMapMessageAttribute = (body == null || body.isEmpty()); + if (checkSpecialMapMessageAttribute) { body = mapMessage.get(SPECIAL_MAP_MESSAGE_ATTRIBUTE); } + if (body != null && !body.isEmpty()) { builder.setBody(body); } if (captureMapMessageAttributes) { - consumeAttributes(mapMessage.getData()::forEach, attributes, checkSpecialMapMessageAttribute); - } - } - - @SuppressWarnings("unchecked") - private void captureLogEvent(ExtendedAttributesBuilder attributes, MutableLogEvent logEvent) { - if (captureMapMessageAttributes) { + // TODO (trask) this could be optimized in 2.9 and later by calling MapMessage.forEach() consumeAttributes( - biConsumer -> - logEvent.getContextData().forEach((s, object) -> biConsumer.accept(s, object)), + mapMessage.getData()::forEach, attributes, - false); + checkSpecialMapMessageAttribute, + LogEventMapper::getMapMessageAttributeKey); } } @@ -211,27 +198,39 @@ private void captureLogEvent(ExtendedAttributesBuilder attributes, MutableLogEve private static void consumeAttributes( Consumer actionConsumer, ExtendedAttributesBuilder attributes, - boolean checkSpecialMapMessageAttribute) { + boolean checkSpecialMapMessageAttribute, + BiFunction> keyProvider) { actionConsumer.accept( (key, value) -> { if (value != null && (!checkSpecialMapMessageAttribute || !key.equals(SPECIAL_MAP_MESSAGE_ATTRIBUTE))) { - consumeEntry(key.toString(), value, attributes); + consumeEntry(key.toString(), value, attributes, keyProvider); } }); } @SuppressWarnings({"unchecked"}) - private static void consumeEntry(String key, Object value, ExtendedAttributesBuilder attributes) { + private static void consumeEntry( + String key, + Object value, + ExtendedAttributesBuilder attributes, + BiFunction> keyProvider) { if (value instanceof String) { - attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.STRING), (String) value); + attributes.put( + (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.STRING), + (String) value); } else if (value instanceof Boolean) { attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.BOOLEAN), (Boolean) value); + (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.BOOLEAN), + (Boolean) value); } else if ((value instanceof Long) || (value instanceof Integer)) { - attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.LONG), (long) value); + attributes.put( + (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.LONG), + (long) value); } else if ((value instanceof Double) || (value instanceof Float)) { - attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE), (double) value); + attributes.put( + (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.DOUBLE), + (double) value); } else if (value instanceof List) { List list = (List) value; if (list.isEmpty()) { @@ -241,45 +240,72 @@ private static void consumeEntry(String key, Object value, ExtendedAttributesBui Object first = list.get(0); if (first instanceof String) { attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.STRING_ARRAY), + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.STRING_ARRAY), (List) value); } else if (first instanceof Boolean) { attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.BOOLEAN_ARRAY), + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.BOOLEAN_ARRAY), (List) value); } else if ((first instanceof Long) || (first instanceof Integer)) { attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.LONG_ARRAY), (List) value); + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), + (List) value); } else if ((first instanceof Double) || (first instanceof Float)) { attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE_ARRAY), + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), (List) value); } } else if (value instanceof String[]) { attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.STRING_ARRAY), (String[]) value); + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.STRING_ARRAY), + Arrays.asList((String[]) value)); } else if (value instanceof boolean[]) { + boolean[] arr = (boolean[]) value; + List list = new ArrayList<>(arr.length); + for (boolean f : arr) { + list.add(f); + } attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.BOOLEAN_ARRAY), (boolean[]) value); + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.BOOLEAN_ARRAY), + list); } else if (value instanceof long[]) { + List list = Arrays.stream((long[]) value).boxed().collect(Collectors.toList()); attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.LONG_ARRAY), (long[]) value); + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), + list); } else if (value instanceof int[]) { + List list = + Arrays.stream((int[]) value).mapToLong(i -> i).boxed().collect(Collectors.toList()); attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.LONG_ARRAY), - Arrays.stream((int[]) value).asLongStream().toArray()); + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), + list); } else if (value instanceof double[]) { + List list = Arrays.stream((double[]) value).boxed().collect(Collectors.toList()); attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE_ARRAY), (double[]) value); + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), + list); } else if (value instanceof float[]) { - double[] arr = new double[((float[]) value).length]; - for (int i = 0; i < arr.length; ++i) { - arr[i] = ((float[]) value)[i]; + float[] arr = (float[]) value; + List list = new ArrayList<>(arr.length); + for (float f : arr) { + list.add((double) f); } - attributes.put(getMapMessageAttributeKey(key, ExtendedAttributeType.DOUBLE_ARRAY), arr); + attributes.put( + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), + list); } else if ((value instanceof Map)) { ExtendedAttributesBuilder nestedAttribute = ExtendedAttributes.builder(); - consumeAttributes(((Map) value)::forEach, nestedAttribute, false); + consumeAttributes(((Map) value)::forEach, nestedAttribute, false, keyProvider); attributes.put( getMapMessageAttributeKey(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES), nestedAttribute.build()); @@ -289,22 +315,21 @@ private static void consumeEntry(String key, Object value, ExtendedAttributesBui } // visible for testing + @SuppressWarnings("unchecked") void captureContextDataAttributes(ExtendedAttributesBuilder attributes, T contextData) { if (captureAllContextDataAttributes) { - contextDataAccessor.forEach( - contextData, - (key, value) -> { - if (value != null) { - attributes.put(getContextDataAttributeKey(key, ExtendedAttributeType.STRING), value); - } - }); + consumeAttributes( + biConsumer -> contextDataAccessor.forEach(contextData, biConsumer), + attributes, + false, + LogEventMapper::getContextDataAttributeKey); return; } for (String key : captureContextDataAttributes) { - String value = contextDataAccessor.getValue(contextData, key); + Object value = contextDataAccessor.getValue(contextData, key); if (value != null) { - attributes.put(getContextDataAttributeKey(key, ExtendedAttributeType.STRING), value); + consumeEntry(key, value, attributes, LogEventMapper::getContextDataAttributeKey); } } } diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java index e3ccaa513dd8..46db64dcd4d0 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java @@ -30,10 +30,10 @@ class LogEventMapperTest { @Test void testDefault() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, false, false, emptyList()); - Map contextData = new HashMap<>(); + Map contextData = new HashMap<>(); contextData.put("key1", "value1"); contextData.put("key2", "value2"); ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); @@ -48,10 +48,10 @@ void testDefault() { @Test void testSome() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, false, false, singletonList("key2")); - Map contextData = new HashMap<>(); + Map contextData = new HashMap<>(); contextData.put("key1", "value1"); contextData.put("key2", "value2"); ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); @@ -66,10 +66,10 @@ void testSome() { @Test void testAll() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, false, false, singletonList("*")); - Map contextData = new HashMap<>(); + Map contextData = new HashMap<>(); contextData.put("key1", "value1"); contextData.put("key2", "value2"); ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); @@ -85,7 +85,7 @@ void testAll() { @Test void testCaptureMapMessageDisabled() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, false, false, singletonList("*")); @@ -107,7 +107,7 @@ void testCaptureMapMessageDisabled() { @Test void testCaptureMapMessageWithSpecialAttribute() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, true, false, singletonList("*")); @@ -130,7 +130,7 @@ void testCaptureMapMessageWithSpecialAttribute() { @Test void testCaptureMapMessageWithoutSpecialAttribute() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, true, false, singletonList("*")); @@ -155,7 +155,7 @@ void testCaptureMapMessageWithoutSpecialAttribute() { @Test void testCaptureStructuredDataMessage() { // given - LogEventMapper> mapper = + LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, true, false, singletonList("*")); @@ -177,17 +177,17 @@ void testCaptureStructuredDataMessage() { attributeEntry("log4j.map_message.message", "value2")); } - private enum ContextDataAccessorImpl implements ContextDataAccessor> { + private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { INSTANCE; @Override @Nullable - public String getValue(Map contextData, String key) { + public Object getValue(Map contextData, String key) { return contextData.get(key); } @Override - public void forEach(Map contextData, BiConsumer action) { + public void forEach(Map contextData, BiConsumer action) { contextData.forEach(action); } } From 1d03c08c13ddf5df5f8535d8a74da13bb16b8e36 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 6 May 2025 14:59:35 +0200 Subject: [PATCH 04/13] more stuff --- .../v2_17/internal/LogEventMapper.java | 33 ++++++-- .../AbstractOpenTelemetryAppenderTest.java | 78 +++++++++++++++++++ .../v2_17/OpenTelemetryAppenderTest.java | 7 ++ .../v2_17/internal/LogEventMapperTest.java | 34 ++++++++ 4 files changed, 145 insertions(+), 7 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index 85d30ea9b271..55ea9b0fcecc 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -223,14 +223,22 @@ private static void consumeEntry( attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.BOOLEAN), (Boolean) value); - } else if ((value instanceof Long) || (value instanceof Integer)) { + } else if ((value instanceof Long)) { attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.LONG), - (long) value); - } else if ((value instanceof Double) || (value instanceof Float)) { + (Long) value); + } else if ((value instanceof Integer)) { + attributes.put( + (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.LONG), + ((Integer) value).longValue()); + } else if (value instanceof Double) { attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.DOUBLE), - (double) value); + (Double) value); + } else if (value instanceof Float) { + attributes.put( + (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.DOUBLE), + ((Float) value).doubleValue()); } else if (value instanceof List) { List list = (List) value; if (list.isEmpty()) { @@ -248,12 +256,22 @@ private static void consumeEntry( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.BOOLEAN_ARRAY), (List) value); - } else if ((first instanceof Long) || (first instanceof Integer)) { + } else if (first instanceof Integer) { + attributes.put( + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), + ((List) value).stream().map(Integer::longValue).collect(Collectors.toList())); + } else if (first instanceof Long) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), (List) value); - } else if ((first instanceof Double) || (first instanceof Float)) { + } else if (first instanceof Float) { + attributes.put( + (ExtendedAttributeKey>) + keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), + ((List) value).stream().map(Float::doubleValue).collect(Collectors.toList())); + } else if (first instanceof Double) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), @@ -307,7 +325,8 @@ private static void consumeEntry( ExtendedAttributesBuilder nestedAttribute = ExtendedAttributes.builder(); consumeAttributes(((Map) value)::forEach, nestedAttribute, false, keyProvider); attributes.put( - getMapMessageAttributeKey(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES), + (ExtendedAttributeKey) + keyProvider.apply(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES), nestedAttribute.build()); } else { throw new IllegalArgumentException("Unrecognized value type: " + value.getClass()); diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java index 5a7373e24fde..8e5df549d861 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java @@ -20,17 +20,22 @@ import static io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes.THREAD_NAME; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -40,6 +45,7 @@ import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.StringMapMessage; import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.spi.ObjectThreadContextMap; import org.assertj.core.api.AbstractLongAssert; import org.assertj.core.api.AssertAccess; import org.junit.jupiter.api.AfterAll; @@ -206,6 +212,78 @@ void logStringMapMessage() { equalTo(stringKey("log4j.map_message.key2"), "val2")))); } + @Test + void withExtendedAttributes() { + StringMapMessage message = new StringMapMessage(); + message.put("key1", "val1"); + message.put("key2", "val2"); + + ObjectThreadContextMap contextMap = + (ObjectThreadContextMap) ThreadContext.getThreadContextMap(); + + contextMap.putValue("integer", 10); + contextMap.putValue("string", "hello"); + contextMap.putValue("double", 11.1d); + + Map map = new HashMap<>(); + map.put("string1", "1"); + map.put("string2", "2"); + contextMap.putValue("map", map); + + contextMap.putValue("intArray", new int[] {1, 2, 3}); + contextMap.putValue("floatList", Arrays.asList(10f, 20f, 30f)); + + logger.info(message); + + executeAfterLogsExecution(); + + getTesting() + .waitAndAssertLogRecords( + logRecord -> + logRecord + .hasResource(resource) + .hasInstrumentationScope(instrumentationScopeInfo) + .hasAttributesSatisfying( + addLocationAttributes( + "withExtendedAttributes", + equalTo(THREAD_NAME, Thread.currentThread().getName()), + equalTo(THREAD_ID, Thread.currentThread().getId()), + equalTo(stringKey("log4j.map_message.key1"), "val1"), + equalTo(stringKey("log4j.map_message.key2"), "val2"))) + .isInstanceOf(ExtendedLogRecordData.class) + .satisfies( + logRecordData -> { + ExtendedLogRecordData extendedLogRecord = + (ExtendedLogRecordData) logRecordData; + Map, Object> extendedAttributes = + extendedLogRecord.getExtendedAttributes().asMap(); + assertThat(extendedAttributes) + .containsEntry(ExtendedAttributeKey.stringKey("string"), "hello"); + assertThat(extendedAttributes) + .containsEntry(ExtendedAttributeKey.longKey("integer"), 10L); + assertThat(extendedAttributes) + .containsEntry(ExtendedAttributeKey.doubleKey("double"), 11.1d); + assertThat(extendedAttributes) + .containsEntry( + ExtendedAttributeKey.longArrayKey("intArray"), + Arrays.asList(1L, 2L, 3L)); + assertThat(extendedAttributes) + .containsEntry( + ExtendedAttributeKey.doubleArrayKey("floatList"), + Arrays.asList(10d, 20d, 30d)); + + Map, Object> expected = new HashMap<>(); + expected.put(ExtendedAttributeKey.stringKey("string1"), "1"); + expected.put(ExtendedAttributeKey.stringKey("string2"), "2"); + + ExtendedAttributes actual = + (ExtendedAttributes) + extendedAttributes.get( + ExtendedAttributeKey.extendedAttributesKey("map")); + assertThat(actual.asMap()).isEqualTo(expected); + })); + } + @Test void logStringMapMessageWithSpecialAttribute() { StringMapMessage message = new StringMapMessage(); diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java index 3b713dcb1e67..93aa5323aa6f 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java @@ -61,4 +61,11 @@ void logWithSpan() { // Does not work for log replay, but it is not likely to oc ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()), equalTo(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()))); } + + @Test + @Override + @SuppressWarnings("RedundantOverride)") + void withExtendedAttributes() { + super.withExtendedAttributes(); + } } diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java index 46db64dcd4d0..a37de3150db2 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java @@ -14,9 +14,11 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; @@ -177,6 +179,38 @@ void testCaptureStructuredDataMessage() { attributeEntry("log4j.map_message.message", "value2")); } + @Test + void testObjects() { + // given + LogEventMapper> mapper = + new LogEventMapper<>( + ContextDataAccessorImpl.INSTANCE, false, false, true, false, singletonList("*")); + + Map map = new HashMap<>(); + Map contextData = new HashMap<>(); + contextData.put("key1", "value1"); + contextData.put("key2", new String[] {"one", "two", "three"}); + map.put("fn", "Joe"); + map.put("ln", "Smitty"); + contextData.put("key3", map); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); + + // when + mapper.captureContextDataAttributes(attributes, contextData); + + // then + ExtendedAttributes result = attributes.build(); + assertThat(result.get(ExtendedAttributeKey.stringKey("key1"))).isEqualTo("value1"); + assertThat(result.get(ExtendedAttributeKey.stringArrayKey("key2"))) + .isEqualTo(Arrays.asList("one", "two", "three")); + + Map, Object> expected = new HashMap<>(); + expected.put(ExtendedAttributeKey.stringKey("fn"), "Joe"); + expected.put(ExtendedAttributeKey.stringKey("ln"), "Smitty"); + assertThat(result.get(ExtendedAttributeKey.extendedAttributesKey("key3")).asMap()) + .isEqualTo(expected); + } + private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { INSTANCE; From 056b61fec531182619a63989b39af98706f90e12 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 6 May 2025 15:21:54 +0200 Subject: [PATCH 05/13] improve test --- .../v2_17/internal/LogEventMapperTest.java | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java index a37de3150db2..09d205458056 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java @@ -180,19 +180,44 @@ void testCaptureStructuredDataMessage() { } @Test - void testObjects() { + void testObjectsInContextData() { // given LogEventMapper> mapper = new LogEventMapper<>( ContextDataAccessorImpl.INSTANCE, false, false, true, false, singletonList("*")); - Map map = new HashMap<>(); Map contextData = new HashMap<>(); - contextData.put("key1", "value1"); - contextData.put("key2", new String[] {"one", "two", "three"}); - map.put("fn", "Joe"); - map.put("ln", "Smitty"); - contextData.put("key3", map); + + // scalars + contextData.put("string", "value"); + contextData.put("int", 10); + contextData.put("long", 11L); + contextData.put("float", 12f); + contextData.put("double", 13d); + contextData.put("boolean", false); + + // arrays + contextData.put("stringArray", new String[] {"one", "two", "three"}); + contextData.put("intArray", new int[] {1, 2, 3}); + contextData.put("longArray", new long[] {4L, 5L, 6L}); + contextData.put("floatArray", new float[] {7f, 8f, 9f}); + contextData.put("doubleArray", new double[] {10d, 11d, 12d}); + contextData.put("booleanArray", new boolean[] {true, false, false}); + + // lists + contextData.put("stringList", Arrays.asList("one", "two", "three")); + contextData.put("intList", Arrays.asList(1, 2, 3)); + contextData.put("longList", Arrays.asList(4L, 5L, 6L)); + contextData.put("floatList", Arrays.asList(7f, 8f, 9f)); + contextData.put("doubleList", Arrays.asList(10d, 11d, 12d)); + contextData.put("booleanList", Arrays.asList(true, false, false)); + + Map map = new HashMap<>(); + map.put("entry1", "value1"); + map.put("entry2", 28); + map.put("entry3", new int[] {1, 2, 3}); + contextData.put("map", map); + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); // when @@ -200,15 +225,49 @@ void testObjects() { // then ExtendedAttributes result = attributes.build(); - assertThat(result.get(ExtendedAttributeKey.stringKey("key1"))).isEqualTo("value1"); - assertThat(result.get(ExtendedAttributeKey.stringArrayKey("key2"))) + + // scalars + assertThat(result.get(ExtendedAttributeKey.stringKey("string"))).isEqualTo("value"); + assertThat(result.get(ExtendedAttributeKey.longKey("int"))).isEqualTo(10L); + assertThat(result.get(ExtendedAttributeKey.longKey("long"))).isEqualTo(11L); + assertThat(result.get(ExtendedAttributeKey.doubleKey("float"))).isEqualTo(12d); + assertThat(result.get(ExtendedAttributeKey.doubleKey("double"))).isEqualTo(13f); + assertThat(result.get(ExtendedAttributeKey.booleanKey("boolean"))).isEqualTo(false); + + assertThat(result.get(ExtendedAttributeKey.stringArrayKey("stringArray"))) .isEqualTo(Arrays.asList("one", "two", "three")); + assertThat(result.get(ExtendedAttributeKey.longArrayKey("intArray"))) + .isEqualTo(Arrays.asList(1L, 2L, 3L)); + assertThat(result.get(ExtendedAttributeKey.longArrayKey("longArray"))) + .isEqualTo(Arrays.asList(4L, 5L, 6L)); + assertThat(result.get(ExtendedAttributeKey.doubleArrayKey("floatArray"))) + .isEqualTo(Arrays.asList(7d, 8d, 9d)); + assertThat(result.get(ExtendedAttributeKey.doubleArrayKey("doubleArray"))) + .isEqualTo(Arrays.asList(10d, 11d, 12d)); + assertThat(result.get(ExtendedAttributeKey.booleanArrayKey("booleanArray"))) + .isEqualTo(Arrays.asList(true, false, false)); + + assertThat(result.get(ExtendedAttributeKey.stringArrayKey("stringList"))) + .isEqualTo(Arrays.asList("one", "two", "three")); + assertThat(result.get(ExtendedAttributeKey.longArrayKey("intList"))) + .isEqualTo(Arrays.asList(1L, 2L, 3L)); + assertThat(result.get(ExtendedAttributeKey.longArrayKey("longList"))) + .isEqualTo(Arrays.asList(4L, 5L, 6L)); + assertThat(result.get(ExtendedAttributeKey.doubleArrayKey("floatList"))) + .isEqualTo(Arrays.asList(7d, 8d, 9d)); + assertThat(result.get(ExtendedAttributeKey.doubleArrayKey("doubleList"))) + .isEqualTo(Arrays.asList(10d, 11d, 12d)); + assertThat(result.get(ExtendedAttributeKey.booleanArrayKey("booleanList"))) + .isEqualTo(Arrays.asList(true, false, false)); Map, Object> expected = new HashMap<>(); - expected.put(ExtendedAttributeKey.stringKey("fn"), "Joe"); - expected.put(ExtendedAttributeKey.stringKey("ln"), "Smitty"); - assertThat(result.get(ExtendedAttributeKey.extendedAttributesKey("key3")).asMap()) - .isEqualTo(expected); + expected.put(ExtendedAttributeKey.stringKey("entry1"), "value1"); + expected.put(ExtendedAttributeKey.longKey("entry2"), 28L); + expected.put(ExtendedAttributeKey.longArrayKey("entry3"), Arrays.asList(1L, 2L, 3L)); + + ExtendedAttributes actual = result.get(ExtendedAttributeKey.extendedAttributesKey("map")); + assertThat(actual).isNotNull(); + assertThat(actual.asMap()).containsExactlyInAnyOrderEntriesOf(expected); } private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { From 21b636bd18d80daa0bf7b4b74cf6a9920f7f2d4c Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 6 May 2025 15:23:03 +0200 Subject: [PATCH 06/13] nn --- .../AbstractOpenTelemetryAppenderTest.java | 78 ------------------- 1 file changed, 78 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java index 8e5df549d861..5a7373e24fde 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/AbstractOpenTelemetryAppenderTest.java @@ -20,22 +20,17 @@ import static io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes.THREAD_NAME; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; -import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -45,7 +40,6 @@ import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.StringMapMessage; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.spi.ObjectThreadContextMap; import org.assertj.core.api.AbstractLongAssert; import org.assertj.core.api.AssertAccess; import org.junit.jupiter.api.AfterAll; @@ -212,78 +206,6 @@ void logStringMapMessage() { equalTo(stringKey("log4j.map_message.key2"), "val2")))); } - @Test - void withExtendedAttributes() { - StringMapMessage message = new StringMapMessage(); - message.put("key1", "val1"); - message.put("key2", "val2"); - - ObjectThreadContextMap contextMap = - (ObjectThreadContextMap) ThreadContext.getThreadContextMap(); - - contextMap.putValue("integer", 10); - contextMap.putValue("string", "hello"); - contextMap.putValue("double", 11.1d); - - Map map = new HashMap<>(); - map.put("string1", "1"); - map.put("string2", "2"); - contextMap.putValue("map", map); - - contextMap.putValue("intArray", new int[] {1, 2, 3}); - contextMap.putValue("floatList", Arrays.asList(10f, 20f, 30f)); - - logger.info(message); - - executeAfterLogsExecution(); - - getTesting() - .waitAndAssertLogRecords( - logRecord -> - logRecord - .hasResource(resource) - .hasInstrumentationScope(instrumentationScopeInfo) - .hasAttributesSatisfying( - addLocationAttributes( - "withExtendedAttributes", - equalTo(THREAD_NAME, Thread.currentThread().getName()), - equalTo(THREAD_ID, Thread.currentThread().getId()), - equalTo(stringKey("log4j.map_message.key1"), "val1"), - equalTo(stringKey("log4j.map_message.key2"), "val2"))) - .isInstanceOf(ExtendedLogRecordData.class) - .satisfies( - logRecordData -> { - ExtendedLogRecordData extendedLogRecord = - (ExtendedLogRecordData) logRecordData; - Map, Object> extendedAttributes = - extendedLogRecord.getExtendedAttributes().asMap(); - assertThat(extendedAttributes) - .containsEntry(ExtendedAttributeKey.stringKey("string"), "hello"); - assertThat(extendedAttributes) - .containsEntry(ExtendedAttributeKey.longKey("integer"), 10L); - assertThat(extendedAttributes) - .containsEntry(ExtendedAttributeKey.doubleKey("double"), 11.1d); - assertThat(extendedAttributes) - .containsEntry( - ExtendedAttributeKey.longArrayKey("intArray"), - Arrays.asList(1L, 2L, 3L)); - assertThat(extendedAttributes) - .containsEntry( - ExtendedAttributeKey.doubleArrayKey("floatList"), - Arrays.asList(10d, 20d, 30d)); - - Map, Object> expected = new HashMap<>(); - expected.put(ExtendedAttributeKey.stringKey("string1"), "1"); - expected.put(ExtendedAttributeKey.stringKey("string2"), "2"); - - ExtendedAttributes actual = - (ExtendedAttributes) - extendedAttributes.get( - ExtendedAttributeKey.extendedAttributesKey("map")); - assertThat(actual.asMap()).isEqualTo(expected); - })); - } - @Test void logStringMapMessageWithSpecialAttribute() { StringMapMessage message = new StringMapMessage(); From 051b789fb06bad0ab8863cd95a1e120a1968bc5a Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 6 May 2025 15:36:02 +0200 Subject: [PATCH 07/13] nn --- .../appender/v2_17/OpenTelemetryAppenderTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java index 93aa5323aa6f..b20cae81297a 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java @@ -11,8 +11,6 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.spi.ObjectThreadContextMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -43,9 +41,6 @@ void logWithSpan() { // Does not work for log replay, but it is not likely to oc testing.runWithSpan( "span1", () -> { - ObjectThreadContextMap map = - (ObjectThreadContextMap) ThreadContext.getThreadContextMap(); - map.putValue("stuff", new int[] {1, 2, 3}); logger.info("log message"); return Span.current(); }); @@ -61,11 +56,4 @@ void logWithSpan() { // Does not work for log replay, but it is not likely to oc ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()), equalTo(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()))); } - - @Test - @Override - @SuppressWarnings("RedundantOverride)") - void withExtendedAttributes() { - super.withExtendedAttributes(); - } } From cb43c1424777be4ec0f6efda64982944cb137742 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sat, 10 May 2025 12:56:48 +0200 Subject: [PATCH 08/13] 1.50.0 --- dependencyManagement/build.gradle.kts | 2 +- .../library/build.gradle.kts | 18 +----------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 4246ee0331f1..32be3e2d83c6 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -5,7 +5,7 @@ plugins { data class DependencySet(val group: String, val version: String, val modules: List) // this line is managed by .github/scripts/update-sdk-version.sh -val otelSdkVersion = "1.49.0" +val otelSdkVersion = "1.50.0" val otelContribVersion = "1.46.0-alpha" val otelSdkAlphaVersion = otelSdkVersion.replaceFirst("(-SNAPSHOT)?$".toRegex(), "-alpha$1") diff --git a/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts b/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts index 636a097d317a..a384aed65a01 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts +++ b/instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts @@ -7,8 +7,7 @@ dependencies { annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.0") implementation(project(":instrumentation:log4j:log4j-context-data:log4j-context-data-2.17:library-autoconfigure")) - implementation("io.opentelemetry:opentelemetry-api-incubator:1.50.0-SNAPSHOT") - implementation("io.opentelemetry:opentelemetry-sdk-logs:1.50.0-SNAPSHOT") + implementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testLibrary("com.lmax:disruptor:3.3.4") @@ -19,17 +18,6 @@ dependencies { } } -repositories { - mavenLocal() - mavenCentral() -} - -configurations.all { - resolutionStrategy { - force("io.opentelemetry:opentelemetry-sdk:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-sdk-logs:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-sdk-common:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-api:1.50.0-SNAPSHOT", "io.opentelemetry:opentelemetry-api-incubator:1.50.0-alpha-SNAPSHOT") - } -} - tasks { withType().configureEach { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) @@ -44,7 +32,3 @@ tasks { dependsOn(testAsyncLogger) } } - -tasks.test { - systemProperty("log4j2.threadContextMap", "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap") -} From 374ad4ca7eda9ed49c7e63e8d7cc93eaf72a1769 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sat, 10 May 2025 13:20:18 +0200 Subject: [PATCH 09/13] remove some unchecked stuff --- .../v2_17/internal/LogEventMapper.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index 55ea9b0fcecc..c793d6630d4c 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -161,7 +161,6 @@ public void mapLogEvent( } // visible for testing - @SuppressWarnings("unchecked") void captureMessage( LogRecordBuilder builder, ExtendedAttributesBuilder attributes, Message message) { if (message == null) { @@ -194,17 +193,17 @@ void captureMessage( } } - @SuppressWarnings("rawtypes") private static void consumeAttributes( - Consumer actionConsumer, + // Consumes an action on an entry, like map::forEach + Consumer> entryActionConsumer, ExtendedAttributesBuilder attributes, boolean checkSpecialMapMessageAttribute, BiFunction> keyProvider) { - actionConsumer.accept( + entryActionConsumer.accept( (key, value) -> { if (value != null && (!checkSpecialMapMessageAttribute || !key.equals(SPECIAL_MAP_MESSAGE_ATTRIBUTE))) { - consumeEntry(key.toString(), value, attributes, keyProvider); + consumeEntry(key, value, attributes, keyProvider); } }); } @@ -323,7 +322,12 @@ private static void consumeEntry( list); } else if ((value instanceof Map)) { ExtendedAttributesBuilder nestedAttribute = ExtendedAttributes.builder(); - consumeAttributes(((Map) value)::forEach, nestedAttribute, false, keyProvider); + Map nestedMap = (Map) value; + consumeAttributes( + consumer -> nestedMap.forEach((k, v) -> consumer.accept(k.toString(), v)), + nestedAttribute, + false, + keyProvider); attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES), @@ -334,11 +338,10 @@ private static void consumeEntry( } // visible for testing - @SuppressWarnings("unchecked") void captureContextDataAttributes(ExtendedAttributesBuilder attributes, T contextData) { if (captureAllContextDataAttributes) { consumeAttributes( - biConsumer -> contextDataAccessor.forEach(contextData, biConsumer), + entryConsumer -> contextDataAccessor.forEach(contextData, entryConsumer), attributes, false, LogEventMapper::getContextDataAttributeKey); From 9c96e8d6cbf3a15d6d886338f5e7db5acb243511 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sat, 10 May 2025 15:51:47 +0200 Subject: [PATCH 10/13] fix cmpl --- .../instrumentation/log4j/appender/v2_17/Log4jHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java index ca2b49ce1695..329dc67016e5 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java @@ -30,7 +30,7 @@ public final class Log4jHelper { - private static final LogEventMapper> mapper; + private static final LogEventMapper> mapper; private static final boolean captureExperimentalAttributes; private static final MethodHandle stackTraceMethodHandle = getStackTraceMethodHandle(); @@ -147,17 +147,17 @@ private static MethodHandle getStackTraceMethodHandle() { } } - private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { + private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { INSTANCE; @Override @Nullable - public Object getValue(Map contextData, String key) { + public Object getValue(Map contextData, String key) { return contextData.get(key); } @Override - public void forEach(Map contextData, BiConsumer action) { + public void forEach(Map contextData, BiConsumer action) { contextData.forEach(action); } } From 7db43767d824422093325454764fa2fa5269bd75 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Sat, 10 May 2025 16:00:58 +0200 Subject: [PATCH 11/13] fix amybiguous --- .../javaagent/runtimemetrics/java8/JarAnalyzerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/testing/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/testing/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerTest.java index 2156a0eabf2d..5b009b32f89b 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/testing/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerTest.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/testing/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerTest.java @@ -42,7 +42,7 @@ class JarAnalyzerTest { void processUrl_EmitsEvents(URL archiveUrl, Consumer attributesConsumer) { ExtendedLogRecordBuilder builder = mock(ExtendedLogRecordBuilder.class); when(builder.setEventName(eq("package.info"))).thenReturn(builder); - when(builder.setAllAttributes(any())).thenReturn(builder); + when(builder.setAllAttributes(any(Attributes.class))).thenReturn(builder); JarAnalyzer.processUrl(builder, archiveUrl); From 4ff3198ec34ef60a30ffe9baee83f8f865049293 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 13 May 2025 23:59:56 +0200 Subject: [PATCH 12/13] cast to Number --- .../v2_17/internal/LogEventMapper.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index c793d6630d4c..4ab867521425 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -222,22 +222,17 @@ private static void consumeEntry( attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.BOOLEAN), (Boolean) value); - } else if ((value instanceof Long)) { + } else if (value instanceof Byte + || value instanceof Short + || value instanceof Integer + || value instanceof Long) { attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.LONG), - (Long) value); - } else if ((value instanceof Integer)) { - attributes.put( - (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.LONG), - ((Integer) value).longValue()); - } else if (value instanceof Double) { - attributes.put( - (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.DOUBLE), - (Double) value); - } else if (value instanceof Float) { + ((Number) value).longValue()); + } else if (value instanceof Float || value instanceof Double) { attributes.put( (ExtendedAttributeKey) keyProvider.apply(key, ExtendedAttributeType.DOUBLE), - ((Float) value).doubleValue()); + ((Number) value).doubleValue()); } else if (value instanceof List) { List list = (List) value; if (list.isEmpty()) { From 4f45fc4dc6879b46a6992f30ef837e3131ca9889 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Wed, 14 May 2025 00:26:22 +0200 Subject: [PATCH 13/13] handle multi-type list --- .../v2_17/internal/LogEventMapper.java | 25 +++++++++++++------ .../v2_17/internal/LogEventMapperTest.java | 23 +++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java index 4ab867521425..981f3d6f013d 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java @@ -208,7 +208,7 @@ private static void consumeAttributes( }); } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "ReturnValueIgnored"}) private static void consumeEntry( String key, Object value, @@ -240,36 +240,47 @@ private static void consumeEntry( } Object first = list.get(0); + for (int idx = 1; idx < list.size(); ++idx) { + try { + first.getClass().cast(list.get(idx)); + } catch (ClassCastException exception) { + // fallback to a list of strings + list = list.stream().map(Object::toString).collect(Collectors.toList()); + first = list.get(0); + break; + } + } + if (first instanceof String) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.STRING_ARRAY), - (List) value); + (List) list); } else if (first instanceof Boolean) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.BOOLEAN_ARRAY), - (List) value); + (List) list); } else if (first instanceof Integer) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), - ((List) value).stream().map(Integer::longValue).collect(Collectors.toList())); + ((List) list).stream().map(Integer::longValue).collect(Collectors.toList())); } else if (first instanceof Long) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.LONG_ARRAY), - (List) value); + (List) list); } else if (first instanceof Float) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), - ((List) value).stream().map(Float::doubleValue).collect(Collectors.toList())); + ((List) list).stream().map(Float::doubleValue).collect(Collectors.toList())); } else if (first instanceof Double) { attributes.put( (ExtendedAttributeKey>) keyProvider.apply(key, ExtendedAttributeType.DOUBLE_ARRAY), - (List) value); + (List) list); } } else if (value instanceof String[]) { attributes.put( diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java index 09d205458056..afce689ca97e 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import org.apache.logging.log4j.message.StringMapMessage; import org.apache.logging.log4j.message.StructuredDataMessage; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; class LogEventMapperTest { @@ -270,6 +271,28 @@ void testObjectsInContextData() { assertThat(actual.asMap()).containsExactlyInAnyOrderEntriesOf(expected); } + @Test + void testMixedTypeListAttribute() { + // given + LogEventMapper> mapper = + new LogEventMapper<>( + ContextDataAccessorImpl.INSTANCE, false, false, true, false, singletonList("*")); + + Map contextData = new HashMap<>(); + contextData.put("stringList", Arrays.asList("one", 2, "three")); + + ExtendedAttributesBuilder attributes = ExtendedAttributes.builder(); + + // when + mapper.captureContextDataAttributes(attributes, contextData); + + // then + ExtendedAttributes result = attributes.build(); + + Assertions.assertThat(result.get(ExtendedAttributeKey.stringArrayKey("stringList"))) + .isEqualTo(Arrays.asList("one", "2", "three")); + } + private enum ContextDataAccessorImpl implements ContextDataAccessor, Object> { INSTANCE;