Inject span context into log4j2 2.13.2+ context.
ADD
instrumentation/log4j/log4j-2.13.2/library/README.md
+# Log4j 2 Integration
+
+This module integrates instrumentation with Log4j 2 by injecting the trace ID and span ID from a
+mounted span into Log4j's [context data](https://logging.apache.org/log4j/2.x/manual/thread-context.html).
+
+To use it, just add the module to your application's runtime classpath.
+
+**Maven**
+
+```xml
+<dependencies>
+ <dependency>
+ <groupId>io.opentelemetry.instrumentation</groupId>
+ <artifactId>opentelemetry-log4j-2.13.2</artifactId>
+ <version>0.7.0-SNAPSHOT</version>
+ <scope>runtime</scope>
+ </dependency>
+</dependencies>
+```
+
+**Gradle**
+
+```kotlin
+dependencies {
+ runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-log4j-2.13.2:0.7.0-SNAPSHOT")
+}
+```
+
+Log4j will automatically pick up our integration and will have these keys added to the context when
+a log statement is made when a span is active.
+
+- `traceId`
+- `spanId`
+- `traceFlags`
+
+You can use these keys when defining an appender in your `log4j.xml` configuration, for example
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration>
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} traceId: %X{traceId} spanId: %X{spanId} - %msg%n" />
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root>
+ <AppenderRef ref="Console" level="All" />
+ </Root>
+ </Loggers>
+</Configuration>
+```

ADD
instrumentation/log4j/log4j-2.13.2/library/log4j-2.13.2-library.gradle
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+ javaSubPackage = 'log4j.v2_13_2'
+}
+
+apply from: "$rootDir/gradle/instrumentation-library.gradle"
+apply plugin: 'org.unbroken-dome.test-sets'
+
+testSets {
+ latestDepTest {
+ dirName = 'test'
+ }
+}
+
+dependencies {
+ compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2'
+
+ annotationProcessor deps.autoservice
+ compileOnly deps.autoservice
+
+ testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2'
+ testAnnotationProcessor group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2'
+
+ latestDepTestImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '+'
+}

ADD
instrumentation/log4j/log4j-2.13.2/library/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/OpenTelemetryContextDataProvider.java
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opentelemetry.instrumentation.log4j.v2_13_2;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.trace.Span;
+import io.opentelemetry.trace.SpanContext;
+import io.opentelemetry.trace.TracingContextUtils;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.logging.log4j.core.util.ContextDataProvider;
+
+/**
+ * Implementation of Log4j 2's {@link ContextDataProvider} which is loaded via SPI. {@link
+ * #supplyContextData()} is called when a log entry is created.
+ */
+@AutoService(ContextDataProvider.class)
+public class OpenTelemetryContextDataProvider implements ContextDataProvider {
+
+ /**
+ * Returns context from the current span when available.
+ *
+ * @return A map containing string versions of the traceId, spanId, and traceFlags, which can then
+ * be accessed from layout components
+ */
+ @Override
+ public Map<String, String> supplyContextData() {
+ Span currentSpan = TracingContextUtils.getCurrentSpan();
+ if (!currentSpan.getContext().isValid()) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, String> contextData = new HashMap<>();
+ SpanContext spanContext = currentSpan.getContext();
+ contextData.put("traceId", spanContext.getTraceId().toLowerBase16());
+ contextData.put("spanId", spanContext.getSpanId().toLowerBase16());
+ contextData.put("traceFlags", spanContext.getTraceFlags().toLowerBase16());
+ return contextData;
+ }
+}

ADD
instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/io/opentelemetry/instrumentation/log4j/v2_13_2/Log4j2Test.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opentelemetry.instrumentation.log4j.v2_13_2
+
+import io.opentelemetry.auto.test.utils.TraceUtils
+import io.opentelemetry.trace.Span
+import io.opentelemetry.trace.TracingContextUtils
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import spock.lang.Specification
+
+class Log4j2Test extends Specification {
+
+ private static final Logger logger = LogManager.getLogger("TestLogger")
+
+ def cleanup() {
+ ListAppender.get().clearEvents()
+ }
+
+ def "no ids when no span"() {
+ when:
+ logger.info("log message 1")
+ logger.info("log message 2")
+
+ def events = ListAppender.get().getEvents()
+
+ then:
+ events.size() == 2
+ events[0].message.formattedMessage == "log message 1"
+ events[0].getContextData().getValue("traceId") == null
+ events[0].getContextData().getValue("spanId") == null
+ events[0].getContextData().getValue("traceFlags") == null
+
+ events[1].message.formattedMessage == "log message 2"
+ events[1].getContextData().getValue("traceId") == null
+ events[1].getContextData().getValue("spanId") == null
+ events[1].getContextData().getValue("traceFlags") == null
+ }
+
+ def "ids when span"() {
+ when:
+ Span span1
+ TraceUtils.runUnderTrace("test") {
+ span1 = TracingContextUtils.currentSpan
+ logger.info("log message 1")
+ }
+
+ logger.info("log message 2")
+
+ Span span2
+ TraceUtils.runUnderTrace("test 2") {
+ span2 = TracingContextUtils.currentSpan
+ logger.info("log message 3")
+ }
+
+ def events = ListAppender.get().getEvents()
+
+ then:
+ events.size() == 3
+ events[0].message.formattedMessage == "log message 1"
+ events[0].getContextData().getValue("traceId") == span1.context.traceId.toLowerBase16()
+ events[0].getContextData().getValue("spanId") == span1.context.spanId.toLowerBase16()
+ events[0].getContextData().getValue("traceFlags") == span1.context.traceFlags.toLowerBase16()
+
+ events[1].message.formattedMessage == "log message 2"
+ events[1].getContextData().getValue("traceId") == null
+ events[1].getContextData().getValue("spanId") == null
+ events[1].getContextData().getValue("traceFlags") == null
+
+ events[2].message.formattedMessage == "log message 3"
+ events[2].getContextData().getValue("traceId") == span2.context.traceId.toLowerBase16()
+ events[2].getContextData().getValue("spanId") == span2.context.spanId.toLowerBase16()
+ events[2].getContextData().getValue("traceFlags") == span2.context.traceFlags.toLowerBase16()
+ }
+}

ADD
instrumentation/log4j/log4j-2.13.2/library/src/test/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opentelemetry.instrumentation.log4j.v2_13_2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+@Plugin(
+ name = "ListAppender",
+ category = Core.CATEGORY_NAME,
+ elementType = Appender.ELEMENT_TYPE,
+ printObject = true)
+public class ListAppender extends AbstractAppender {
+
+ public static ListAppender get() {
+ return INSTANCE;
+ }
+
+ private static final ListAppender INSTANCE = new ListAppender();
+
+ private final List<LogEvent> events = Collections.synchronizedList(new ArrayList<>());
+
+ public ListAppender() {
+ super("ListAppender", null, null, true, Property.EMPTY_ARRAY);
+ }
+
+ public List<LogEvent> getEvents() {
+ return events;
+ }
+
+ public void clearEvents() {
+ events.clear();
+ }
+
+ @Override
+ public void append(LogEvent logEvent) {
+ events.add(logEvent);
+ }
+
+ @PluginFactory
+ public static ListAppender createAppender(@PluginAttribute("name") String name) {
+ if (!name.equals("ListAppender")) {
+ throw new IllegalArgumentException(
+ "Use name=\"ListAppender\" in log4j2-test.xml instead of " + name);
+ }
+ return INSTANCE;
+ }
+}

ADD
instrumentation/log4j/log4j-2.13.2/library/src/test/resources/log4j2-test.xml
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" packages="com.example.appender">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} traceId: %X{traceId} spanId: %X{spanId} - %msg%n" />
+ </Console>
+ <ListAppender name="ListAppender" />
+ </Appenders>
+ <Loggers>
+ <Logger name="TestLogger" level="All">
+ <AppenderRef ref="ListAppender" level="All" />
+ <AppenderRef ref="Console" level="All" />
+ </Logger>
+ <Root>
+ <AppenderRef ref="Console" level="All" />
+ </Root>
+ </Loggers>
+</Configuration>

MODIFY
settings.gradle
settings.gradle
@@ -112,6 +112,7 @@ include ':instrumentation:lettuce:lettuce-5.0'
include ':instrumentation:lettuce:lettuce-5.1'
include ':instrumentation:log4j:log4j-1.1'
include ':instrumentation:log4j:log4j-2.0'
+include ':instrumentation:log4j:log4j-2.13.2:library'
include ':instrumentation:logback-1.0'
include ':instrumentation:mongo:mongo-3.1'
include ':instrumentation:mongo:mongo-3.7'

Inject span context into log4j2 2.13.2+ context. (#735)