diff --git a/instrumentation/log4j/log4j-2.13.2/library/README.md b/instrumentation/log4j/log4j-2.13.2/library/README.md new file mode 100644 index 0000000000..875e162618 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/README.md @@ -0,0 +1,52 @@ +# 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 + + + io.opentelemetry.instrumentation + opentelemetry-log4j-2.13.2 + 0.7.0-SNAPSHOT + runtime + + +``` + +**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 + + + + + + + + + + + + + +``` diff --git a/instrumentation/log4j/log4j-2.13.2/library/log4j-2.13.2-library.gradle b/instrumentation/log4j/log4j-2.13.2/library/log4j-2.13.2-library.gradle new file mode 100644 index 0000000000..73de75e19e --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/log4j-2.13.2-library.gradle @@ -0,0 +1,25 @@ +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: '+' +} diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/OpenTelemetryContextDataProvider.java b/instrumentation/log4j/log4j-2.13.2/library/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/OpenTelemetryContextDataProvider.java new file mode 100644 index 0000000000..e0098234e9 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/OpenTelemetryContextDataProvider.java @@ -0,0 +1,55 @@ +/* + * 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 supplyContextData() { + Span currentSpan = TracingContextUtils.getCurrentSpan(); + if (!currentSpan.getContext().isValid()) { + return Collections.emptyMap(); + } + + Map 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; + } +} diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/io/opentelemetry/instrumentation/log4j/v2_13_2/Log4j2Test.groovy b/instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/io/opentelemetry/instrumentation/log4j/v2_13_2/Log4j2Test.groovy new file mode 100644 index 0000000000..449a82b0f6 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/io/opentelemetry/instrumentation/log4j/v2_13_2/Log4j2Test.groovy @@ -0,0 +1,89 @@ +/* + * 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() + } +} diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/test/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java b/instrumentation/log4j/log4j-2.13.2/library/src/test/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java new file mode 100644 index 0000000000..3bf0772e9b --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/src/test/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java @@ -0,0 +1,71 @@ +/* + * 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 events = Collections.synchronizedList(new ArrayList<>()); + + public ListAppender() { + super("ListAppender", null, null, true, Property.EMPTY_ARRAY); + } + + public List 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; + } +} diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/test/resources/log4j2-test.xml b/instrumentation/log4j/log4j-2.13.2/library/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..f03c05d692 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/src/test/resources/log4j2-test.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index 6a27dc3ab3..700e5db4a7 100644 --- a/settings.gradle +++ b/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'