Change log4j MapMessage attribute names
Changed them from standalone top-level attributes to move them under
`log4j.map_message.*` (e.g. similar to `log4j.context_data.*` and
others).
Also adds doc for log4j javaagent properties.
Also cleans up related tests.
ADD
instrumentation/log4j/log4j-appender-2.17/javaagent/README.md
+# Settings for the Log4j Appender instrumentation
+
+| System property | Type | Default | Description |
+|---|---------|--|------------------------------------------------------|
+| `otel.instrumentation.log4j-appender.experimental-log-attributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. |
+| `otel.instrumentation.log4j-appender.experimental.capture-map-message-attributes` | Boolean | `false` | Enable the capture of `MapMessage` attributes. |
+| `otel.instrumentation.log4j-appender.experimental.capture-marker-attribute` | Boolean | `false` | Enable the capture of Log4j markers as attributes. |
+| `otel.instrumentation.log4j-appender.experimental.capture-context-data-attributes` | String | | List of context data attributes to capture. Use the wildcard character `*` to capture all attributes. |
+
+[source code attributes]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#source-code-attributes

MODIFY
instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/groovy/Log4j2Test.groovy
instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/groovy/Log4j2Test.groovy
@@ -146,8 +146,8 @@ class Log4j2Test extends AgentInstrumentationSpecification {
assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
assertThat(log.getSeverityText()).isEqualTo("INFO")
assertThat(log.getAttributes().size()).isEqualTo(4)
- OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("key1", "val1")
- OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("key2", "val2")
+ OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.map_message.key1", "val1")
+ OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.map_message.key2", "val2")
OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
}
@@ -172,7 +172,7 @@ class Log4j2Test extends AgentInstrumentationSpecification {
assertThat(log.getSeverity()).isEqualTo(Severity.INFO)
assertThat(log.getSeverityText()).isEqualTo("INFO")
assertThat(log.getAttributes().size()).isEqualTo(3)
- OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("key1", "val1")
+ OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry("log4j.map_message.key1", "val1")
OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
OpenTelemetryAssertions.assertThat(log.getAttributes()).containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
}
@@ -198,8 +198,8 @@ class Log4j2Test extends AgentInstrumentationSpecification {
assertThat(log.getSeverityText()).isEqualTo("INFO")
assertThat(log.getAttributes().size()).isEqualTo(4)
OpenTelemetryAssertions.assertThat(log.getAttributes())
- .containsEntry("key1","val1")
- .containsEntry("key2", "val2")
+ .containsEntry("log4j.map_message.key1","val1")
+ .containsEntry("log4j.map_message.key2", "val2")
.containsEntry(SemanticAttributes.THREAD_NAME, Thread.currentThread().getName())
.containsEntry(SemanticAttributes.THREAD_ID, Thread.currentThread().getId())
}

MODIFY
instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts
instrumentation/log4j/log4j-appender-2.17/library/build.gradle.kts
@@ -9,4 +9,5 @@ dependencies {
testImplementation("io.opentelemetry:opentelemetry-sdk-logs")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
+ testImplementation("io.opentelemetry:opentelemetry-sdk-logs-testing")
}

MODIFY
instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java
instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapper.java
@@ -146,9 +146,7 @@ public final class LogEventMapper<T> {
if (value != null
&& (!checkSpecialMapMessageAttribute
|| !key.equals(SPECIAL_MAP_MESSAGE_ATTRIBUTE))) {
- attributes.put(
- mapMessageAttributeKeyCache.computeIfAbsent(key, AttributeKey::stringKey),
- value.toString());
+ attributes.put(getMapMessageAttributeKey(key), value.toString());
}
});
}
@@ -181,6 +179,11 @@ public final class LogEventMapper<T> {
key, k -> AttributeKey.stringKey("log4j.context_data." + k));
}
+ public static AttributeKey<String> getMapMessageAttributeKey(String key) {
+ return mapMessageAttributeKeyCache.computeIfAbsent(
+ key, k -> AttributeKey.stringKey("log4j.map_message." + k));
+ }
+
private static void setThrowable(AttributesBuilder attributes, Throwable throwable) {
// TODO (trask) extract method for recording exception into
// io.opentelemetry:opentelemetry-api-logs

MODIFY
instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java
instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java
package io.opentelemetry.instrumentation.log4j.appender.v2_17;
-import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
-import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.logs.GlobalLoggerProvider;
import io.opentelemetry.api.logs.Severity;
@@ -72,11 +74,11 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
- LogRecordData logData = logDataList.get(0);
- assertThat(logData.getResource()).isEqualTo(resource);
- assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
- assertThat(logData.getBody().asString()).isEqualTo("log message 1");
- assertThat(logData.getAttributes()).isEqualTo(Attributes.empty());
+ assertThat(logDataList.get(0))
+ .hasResource(resource)
+ .hasInstrumentationScope(instrumentationScopeInfo)
+ .hasBody("log message 1")
+ .hasAttributes(Attributes.empty());
}
@Test
@@ -111,22 +113,20 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
- LogRecordData logData = logDataList.get(0);
- assertThat(logData.getResource()).isEqualTo(resource);
- assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
- assertThat(logData.getBody().asString()).isEqualTo("log message 1");
- assertThat(logData.getEpochNanos())
+ assertThat(logDataList.get(0))
+ .hasResource(resource)
+ .hasInstrumentationScope(instrumentationScopeInfo)
+ .hasBody("log message 1")
+ .hasSeverity(Severity.INFO)
+ .hasSeverityText("INFO")
+ .hasAttributesSatisfyingExactly(
+ equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
+ equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "Error!"),
+ satisfies(SemanticAttributes.EXCEPTION_STACKTRACE, v -> v.contains("logWithExtras")));
+
+ assertThat(logDataList.get(0).getEpochNanos())
.isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli()))
.isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli()));
- assertThat(logData.getSeverity()).isEqualTo(Severity.INFO);
- assertThat(logData.getSeverityText()).isEqualTo("INFO");
- assertThat(logData.getAttributes().size()).isEqualTo(3);
- assertThat(logData.getAttributes().get(SemanticAttributes.EXCEPTION_TYPE))
- .isEqualTo(IllegalStateException.class.getName());
- assertThat(logData.getAttributes().get(SemanticAttributes.EXCEPTION_MESSAGE))
- .isEqualTo("Error!");
- assertThat(logData.getAttributes().get(SemanticAttributes.EXCEPTION_STACKTRACE))
- .contains("logWithExtras");
}
@Test
@@ -141,15 +141,13 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
- LogRecordData logData = logDataList.get(0);
- assertThat(logData.getResource()).isEqualTo(resource);
- assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
- assertThat(logData.getBody().asString()).isEqualTo("log message 1");
- assertThat(logData.getAttributes().size()).isEqualTo(2);
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("log4j.context_data.key1")))
- .isEqualTo("val1");
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("log4j.context_data.key2")))
- .isEqualTo("val2");
+ assertThat(logDataList.get(0))
+ .hasResource(resource)
+ .hasInstrumentationScope(instrumentationScopeInfo)
+ .hasBody("log message 1")
+ .hasAttributesSatisfyingExactly(
+ equalTo(stringKey("log4j.context_data.key1"), "val1"),
+ equalTo(stringKey("log4j.context_data.key2"), "val2"));
}
@Test
@@ -161,13 +159,12 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
- LogRecordData logData = logDataList.get(0);
- assertThat(logData.getResource()).isEqualTo(resource);
- assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
- assertThat(logData.getBody().asString()).isEmpty();
- assertThat(logData.getAttributes().size()).isEqualTo(2);
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1");
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("key2"))).isEqualTo("val2");
+ assertThat(logDataList.get(0))
+ .hasResource(resource)
+ .hasInstrumentationScope(instrumentationScopeInfo)
+ .hasAttributesSatisfyingExactly(
+ equalTo(stringKey("log4j.map_message.key1"), "val1"),
+ equalTo(stringKey("log4j.map_message.key2"), "val2"));
}
@Test
@@ -179,12 +176,11 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
- LogRecordData logData = logDataList.get(0);
- assertThat(logData.getResource()).isEqualTo(resource);
- assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
- assertThat(logData.getBody().asString()).isEqualTo("val2");
- assertThat(logData.getAttributes().size()).isEqualTo(1);
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1");
+ assertThat(logDataList.get(0))
+ .hasResource(resource)
+ .hasInstrumentationScope(instrumentationScopeInfo)
+ .hasBody("val2")
+ .hasAttributesSatisfyingExactly(equalTo(stringKey("log4j.map_message.key1"), "val1"));
}
@Test
@@ -196,8 +192,7 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
LogRecordData logData = logDataList.get(0);
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("log4j.marker")))
- .isEqualTo(markerName);
+ assertThat(logData.getAttributes().get(stringKey("log4j.marker"))).isEqualTo(markerName);
}
@Test
@@ -209,12 +204,12 @@ class OpenTelemetryAppenderConfigTest {
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
assertThat(logDataList).hasSize(1);
- LogRecordData logData = logDataList.get(0);
- assertThat(logData.getResource()).isEqualTo(resource);
- assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
- assertThat(logData.getBody().asString()).isEqualTo("a message");
- assertThat(logData.getAttributes().size()).isEqualTo(2);
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("key1"))).isEqualTo("val1");
- assertThat(logData.getAttributes().get(AttributeKey.stringKey("key2"))).isEqualTo("val2");
+ assertThat(logDataList.get(0))
+ .hasResource(resource)
+ .hasInstrumentationScope(instrumentationScopeInfo)
+ .hasBody("a message")
+ .hasAttributesSatisfyingExactly(
+ equalTo(stringKey("log4j.map_message.key1"), "val1"),
+ equalTo(stringKey("log4j.map_message.key2"), "val2"));
}
}

MODIFY
instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java
instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/internal/LogEventMapperTest.java
package io.opentelemetry.instrumentation.log4j.appender.v2_17.internal;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.logs.LogRecordBuilder;
@@ -61,7 +60,7 @@ class LogEventMapperTest {
// then
assertThat(attributes.build())
- .containsOnly(entry(AttributeKey.stringKey("log4j.context_data.key2"), "value2"));
+ .containsOnly(attributeEntry("log4j.context_data.key2", "value2"));
}
@Test
@@ -81,8 +80,8 @@ class LogEventMapperTest {
// then
assertThat(attributes.build())
.containsOnly(
- entry(AttributeKey.stringKey("log4j.context_data.key1"), "value1"),
- entry(AttributeKey.stringKey("log4j.context_data.key2"), "value2"));
+ attributeEntry("log4j.context_data.key1", "value1"),
+ attributeEntry("log4j.context_data.key2", "value2"));
}
@Test
@@ -126,7 +125,7 @@ class LogEventMapperTest {
// then
verify(logRecordBuilder).setBody("value2");
- assertThat(attributes.build()).containsOnly(entry(AttributeKey.stringKey("key1"), "value1"));
+ assertThat(attributes.build()).containsOnly(attributeEntry("log4j.map_message.key1", "value1"));
}
@Test
@@ -150,8 +149,8 @@ class LogEventMapperTest {
verify(logRecordBuilder, never()).setBody(anyString());
assertThat(attributes.build())
.containsOnly(
- entry(AttributeKey.stringKey("key1"), "value1"),
- entry(AttributeKey.stringKey("key2"), "value2"));
+ attributeEntry("log4j.map_message.key1", "value1"),
+ attributeEntry("log4j.map_message.key2", "value2"));
}
@Test
@@ -175,8 +174,8 @@ class LogEventMapperTest {
verify(logRecordBuilder).setBody("a message");
assertThat(attributes.build())
.containsOnly(
- entry(AttributeKey.stringKey("key1"), "value1"),
- entry(AttributeKey.stringKey("message"), "value2"));
+ attributeEntry("log4j.map_message.key1", "value1"),
+ attributeEntry("log4j.map_message.message", "value2"));
}
private enum ContextDataAccessorImpl implements ContextDataAccessor<Map<String, String>> {

Change log4j MapMessage attribute names (#7397)

Changed them from standalone top-level attributes to move them under
`log4j.map_message.*` (e.g. similar to `log4j.context_data.*` and
others).

Also adds doc for log4j javaagent properties.

Also cleans up related tests.