diff --git a/README.md b/README.md index 9f701aad6101..ccf7725d3bbf 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ provide the path to a JAR file including an SPI implementation using the system | [khttp](https://khttp.readthedocs.io) | 0.1+ | | [Kubernetes Client](https://github.com/kubernetes-client/java) | 7.0+ | | [Lettuce](https://github.com/lettuce-io/lettuce-core) | 4.0+ | +| [Log4j 2](https://logging.apache.org/log4j/2.x/) | 2.7+ | | [Logback](http://logback.qos.ch/) | 1.0+ | | [MongoDB Drivers](https://mongodb.github.io/mongo-java-driver/) | 3.3+ | | [Netty](https://github.com/netty/netty) | 3.8+ | diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/log/LoggingContextConstants.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/log/LoggingContextConstants.java new file mode 100644 index 000000000000..87b122c633fe --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/log/LoggingContextConstants.java @@ -0,0 +1,39 @@ +/* + * 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.api.log; + +/** + * This class contains several constants used in logging libraries' Mapped Diagnostic Context + * instrumentations. + * + * @see org.slf4j.MDC + * @see org.apache.logging.log4j.ThreadContext + * @see org.apache.log4j.MDC + */ +public final class LoggingContextConstants { + /** Key under which the current trace id will be injected into the context data. */ + public static final String TRACE_ID = "traceId"; + /** Key under which the current span id will be injected into the context data. */ + public static final String SPAN_ID = "spanId"; + /** + * Key under which a boolean indicating whether current span is sampled will be injected into the + * context data. + */ + public static final String SAMPLED = "sampled"; + + private LoggingContextConstants() {} +} diff --git a/instrumentation/log4j/log4j-1.2/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v1_2/Log4j1LoggingEventInstrumentation.java b/instrumentation/log4j/log4j-1.2/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v1_2/Log4j1LoggingEventInstrumentation.java index c186076fcac1..f3659c84e085 100644 --- a/instrumentation/log4j/log4j-1.2/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v1_2/Log4j1LoggingEventInstrumentation.java +++ b/instrumentation/log4j/log4j-1.2/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v1_2/Log4j1LoggingEventInstrumentation.java @@ -16,6 +16,9 @@ package io.opentelemetry.instrumentation.auto.log4j.v1_2; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SAMPLED; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SPAN_ID; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -79,7 +82,7 @@ public static void onExit( @Advice.This LoggingEvent event, @Advice.Argument(0) String key, @Advice.Return(readOnly = false) Object value) { - if ("traceId".equals(key) || "spanId".equals(key) || "sampled".equals(key)) { + if (TRACE_ID.equals(key) || SPAN_ID.equals(key) || SAMPLED.equals(key)) { if (value != null) { // Assume already instrumented event if traceId/spanId/sampled is present. return; @@ -92,13 +95,13 @@ public static void onExit( SpanContext spanContext = span.getContext(); switch (key) { - case "traceId": + case TRACE_ID: value = spanContext.getTraceIdAsHexString(); break; - case "spanId": + case SPAN_ID: value = spanContext.getSpanIdAsHexString(); break; - case "sampled": + case SAMPLED: if (spanContext.isSampled()) { value = "true"; } @@ -128,14 +131,14 @@ public static void onEnter( } // Assume already instrumented event if traceId is present. - if (!mdc.contains("traceId")) { + if (!mdc.contains(TRACE_ID)) { Span span = InstrumentationContext.get(LoggingEvent.class, Span.class).get(event); if (span != null && span.getContext().isValid()) { SpanContext spanContext = span.getContext(); - mdc.put("traceId", spanContext.getTraceIdAsHexString()); - mdc.put("spanId", spanContext.getSpanIdAsHexString()); + mdc.put(TRACE_ID, spanContext.getTraceIdAsHexString()); + mdc.put(SPAN_ID, spanContext.getSpanIdAsHexString()); if (spanContext.isSampled()) { - mdc.put("sampled", "true"); + mdc.put(SAMPLED, "true"); } } } diff --git a/instrumentation/log4j/log4j-2-testing/log4j-2-testing.gradle b/instrumentation/log4j/log4j-2-testing/log4j-2-testing.gradle new file mode 100644 index 000000000000..cb1c03ca2dd8 --- /dev/null +++ b/instrumentation/log4j/log4j-2-testing/log4j-2-testing.gradle @@ -0,0 +1,15 @@ +apply from: "$rootDir/gradle/java.gradle" + +dependencies { + api project(':testing-common') + + api group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.7' + + implementation deps.guava + + implementation deps.groovy + implementation deps.opentelemetryApi + implementation deps.spock + + annotationProcessor group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.7' +} 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-testing/src/main/groovy/Log4j2Test.groovy similarity index 89% rename from instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/io/opentelemetry/instrumentation/log4j/v2_13_2/Log4j2Test.groovy rename to instrumentation/log4j/log4j-2-testing/src/main/groovy/Log4j2Test.groovy index ec4e77a13b91..c98415b0bb3c 100644 --- 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-testing/src/main/groovy/Log4j2Test.groovy @@ -14,24 +14,22 @@ * limitations under the License. */ -package io.opentelemetry.instrumentation.log4j.v2_13_2 - +import io.opentelemetry.auto.test.InstrumentationSpecification import io.opentelemetry.auto.test.utils.TraceUtils +import io.opentelemetry.instrumentation.log4j.v2_13_2.ListAppender 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") +abstract class Log4j2Test extends InstrumentationSpecification { def cleanup() { ListAppender.get().clearEvents() } def "no ids when no span"() { + given: + def logger = LogManager.getLogger("TestLogger") + when: logger.info("log message 1") logger.info("log message 2") @@ -52,6 +50,9 @@ class Log4j2Test extends Specification { } def "ids when span"() { + given: + def logger = LogManager.getLogger("TestLogger") + when: Span span1 TraceUtils.runUnderTrace("test") { 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-testing/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java similarity index 89% rename from instrumentation/log4j/log4j-2.13.2/library/src/test/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java rename to instrumentation/log4j/log4j-2-testing/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java index 3bf0772e9bb6..a5b8b65e60e9 100644 --- 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-testing/src/main/java/io/opentelemetry/instrumentation/log4j/v2_13_2/ListAppender.java @@ -20,17 +20,15 @@ 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, + category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true) public class ListAppender extends AbstractAppender { @@ -41,10 +39,10 @@ public static ListAppender get() { private static final ListAppender INSTANCE = new ListAppender(); - private final List events = Collections.synchronizedList(new ArrayList<>()); + private final List events = Collections.synchronizedList(new ArrayList()); public ListAppender() { - super("ListAppender", null, null, true, Property.EMPTY_ARRAY); + super("ListAppender", null, null, true); } public List getEvents() { diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/test/resources/log4j2-test.xml b/instrumentation/log4j/log4j-2-testing/src/main/resources/log4j2-test.xml similarity index 100% rename from instrumentation/log4j/log4j-2.13.2/library/src/test/resources/log4j2-test.xml rename to instrumentation/log4j/log4j-2-testing/src/main/resources/log4j2-test.xml diff --git a/instrumentation/log4j/log4j-2.13.2/auto/log4j-2.13.2-auto.gradle b/instrumentation/log4j/log4j-2.13.2/auto/log4j-2.13.2-auto.gradle new file mode 100644 index 000000000000..3354b39b0941 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/auto/log4j-2.13.2-auto.gradle @@ -0,0 +1,18 @@ +apply from: "$rootDir/gradle/instrumentation.gradle" + +muzzle { + pass { + group = "org.apache.logging.log4j" + module = "log4j-core" + versions = "[2.13.2,)" + assertInverse = true + } +} + +dependencies { + library group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2' + + implementation project(':instrumentation:log4j:log4j-2.13.2:library') + + testImplementation project(':instrumentation:log4j:log4j-2-testing') +} diff --git a/instrumentation/log4j/log4j-2.13.2/auto/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_13_2/Log4j2MdcInstrumentation.java b/instrumentation/log4j/log4j-2.13.2/auto/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_13_2/Log4j2MdcInstrumentation.java new file mode 100644 index 000000000000..ddfd88e27a0a --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/auto/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_13_2/Log4j2MdcInstrumentation.java @@ -0,0 +1,69 @@ +/* + * 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.auto.log4j.v2_13_2; + +import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.tooling.Instrumenter; +import java.util.Collections; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class Log4j2MdcInstrumentation extends Instrumenter.Default { + public Log4j2MdcInstrumentation() { + super("log4j2", "log4j", "log4j-2.13.2"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + return hasClassesNamed("org.apache.logging.log4j.core.util.ContextDataProvider"); + } + + @Override + public ElementMatcher typeMatcher() { + // we cannot use ContextDataProvider here because one of the classes that we inject implements + // this interface, causing the interface to be loaded while it's being transformed, which leads + // to duplicate class definition error after the interface is transformed and the triggering + // class loader tries to load it. + return named("org.apache.logging.log4j.core.impl.ThreadContextDataInjector"); + } + + @Override + public String[] helperResourceNames() { + return new String[] { + "META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider", + }; + } + + @Override + public String[] helperClassNames() { + return new String[] { + "io.opentelemetry.instrumentation.log4j.v2_13_2.OpenTelemetryContextDataProvider" + }; + } + + @Override + public Map, String> transformers() { + // Nothing to instrument, injecting helper resource & class is enough + return Collections.emptyMap(); + } +} diff --git a/instrumentation/log4j/log4j-2.13.2/auto/src/test/groovy/AutoLog4j2Test.groovy b/instrumentation/log4j/log4j-2.13.2/auto/src/test/groovy/AutoLog4j2Test.groovy new file mode 100644 index 000000000000..47da4221f5f3 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/auto/src/test/groovy/AutoLog4j2Test.groovy @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import io.opentelemetry.auto.test.AgentTestTrait + +class AutoLog4j2Test extends Log4j2Test implements AgentTestTrait { +} diff --git a/instrumentation/log4j/log4j-2.13.2/library/README.md b/instrumentation/log4j/log4j-2.13.2/library/README.md index 875e1626181b..cef9f06382f6 100644 --- a/instrumentation/log4j/log4j-2.13.2/library/README.md +++ b/instrumentation/log4j/log4j-2.13.2/library/README.md @@ -31,7 +31,7 @@ a log statement is made when a span is active. - `traceId` - `spanId` -- `traceFlags` +- `sampled` You can use these keys when defining an appender in your `log4j.xml` configuration, for example 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 index 3469e09f6135..ec0fa655eebc 100644 --- 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 @@ -7,8 +7,5 @@ apply from: "$rootDir/gradle/instrumentation-library.gradle" dependencies { library group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2' - annotationProcessor deps.autoservice - compileOnly deps.autoservice - - testAnnotationProcessor group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.2' + testImplementation project(':instrumentation:log4j:log4j-2-testing') } 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 index f9648cf0f157..8853880eb236 100644 --- 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 @@ -16,7 +16,10 @@ package io.opentelemetry.instrumentation.log4j.v2_13_2; -import com.google.auto.service.AutoService; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SAMPLED; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SPAN_ID; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; + import io.opentelemetry.trace.Span; import io.opentelemetry.trace.SpanContext; import io.opentelemetry.trace.TracingContextUtils; @@ -29,7 +32,6 @@ * 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 { /** @@ -47,10 +49,10 @@ public Map supplyContextData() { Map contextData = new HashMap<>(); SpanContext spanContext = currentSpan.getContext(); - contextData.put("traceId", spanContext.getTraceIdAsHexString()); - contextData.put("spanId", spanContext.getSpanIdAsHexString()); + contextData.put(TRACE_ID, spanContext.getTraceIdAsHexString()); + contextData.put(SPAN_ID, spanContext.getSpanIdAsHexString()); if (spanContext.isSampled()) { - contextData.put("sampled", "true"); + contextData.put(SAMPLED, "true"); } return contextData; } diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider b/instrumentation/log4j/log4j-2.13.2/library/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider new file mode 100644 index 000000000000..e4eb8009d0b4 --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider @@ -0,0 +1 @@ +io.opentelemetry.instrumentation.log4j.v2_13_2.OpenTelemetryContextDataProvider diff --git a/instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/LibraryLog4j2Test.groovy b/instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/LibraryLog4j2Test.groovy new file mode 100644 index 000000000000..cf65e081fb9f --- /dev/null +++ b/instrumentation/log4j/log4j-2.13.2/library/src/test/groovy/LibraryLog4j2Test.groovy @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import io.opentelemetry.auto.test.InstrumentationTestTrait + +class LibraryLog4j2Test extends Log4j2Test implements InstrumentationTestTrait { +} \ No newline at end of file diff --git a/instrumentation/log4j/log4j-2.7/log4j-2.7.gradle b/instrumentation/log4j/log4j-2.7/log4j-2.7.gradle new file mode 100644 index 000000000000..d341732febf8 --- /dev/null +++ b/instrumentation/log4j/log4j-2.7/log4j-2.7.gradle @@ -0,0 +1,15 @@ +apply from: "$rootDir/gradle/instrumentation.gradle" + +muzzle { + pass { + group = "org.apache.logging.log4j" + module = "log4j-core" + versions = "[2.7,2.13.2)" + } +} + +dependencies { + library group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.7' + + testImplementation project(':instrumentation:log4j:log4j-2-testing') +} diff --git a/instrumentation/log4j/log4j-2.7/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_7/Log4j27MdcInstrumentation.java b/instrumentation/log4j/log4j-2.7/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_7/Log4j27MdcInstrumentation.java new file mode 100644 index 000000000000..e630fde23235 --- /dev/null +++ b/instrumentation/log4j/log4j-2.7/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_7/Log4j27MdcInstrumentation.java @@ -0,0 +1,80 @@ +/* + * 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.auto.log4j.v2_7; + +import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.tooling.Instrumenter; +import java.util.Collections; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.logging.log4j.core.ContextDataInjector; + +@AutoService(Instrumenter.class) +public class Log4j27MdcInstrumentation extends Instrumenter.Default { + public Log4j27MdcInstrumentation() { + super("log4j2", "log4j", "log4j-2.7"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "io.opentelemetry.instrumentation.auto.log4j.v2_7.SpanDecoratingContextDataInjector" + }; + } + + @Override + public ElementMatcher classLoaderMatcher() { + return hasClassesNamed("org.apache.logging.log4j.core.impl.ContextDataInjectorFactory") + .and(not(hasClassesNamed("org.apache.logging.log4j.core.util.ContextDataProvider"))); + } + + @Override + public ElementMatcher typeMatcher() { + return named("org.apache.logging.log4j.core.impl.ContextDataInjectorFactory"); + } + + @Override + public Map, String> transformers() { + return Collections.singletonMap( + isMethod() + .and(isPublic()) + .and(isStatic()) + .and(named("createInjector")) + .and(returns(named("org.apache.logging.log4j.core.ContextDataInjector"))), + Log4j27MdcInstrumentation.class.getName() + "$CreateInjectorAdvice"); + } + + public static class CreateInjectorAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return(typing = Typing.DYNAMIC, readOnly = false) ContextDataInjector injector) { + injector = new SpanDecoratingContextDataInjector(injector); + } + } +} diff --git a/instrumentation/log4j/log4j-2.7/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_7/SpanDecoratingContextDataInjector.java b/instrumentation/log4j/log4j-2.7/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_7/SpanDecoratingContextDataInjector.java new file mode 100644 index 000000000000..8f14488830db --- /dev/null +++ b/instrumentation/log4j/log4j-2.7/src/main/java/io/opentelemetry/instrumentation/auto/log4j/v2_7/SpanDecoratingContextDataInjector.java @@ -0,0 +1,66 @@ +/* + * 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.auto.log4j.v2_7; + +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SAMPLED; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SPAN_ID; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; + +import io.opentelemetry.trace.SpanContext; +import io.opentelemetry.trace.TracingContextUtils; +import java.util.List; +import org.apache.logging.log4j.core.ContextDataInjector; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; + +public final class SpanDecoratingContextDataInjector implements ContextDataInjector { + private final ContextDataInjector delegate; + + public SpanDecoratingContextDataInjector(ContextDataInjector delegate) { + this.delegate = delegate; + } + + @Override + public StringMap injectContextData(List list, StringMap stringMap) { + StringMap contextData = delegate.injectContextData(list, stringMap); + + if (contextData.containsKey(TRACE_ID)) { + // Assume already instrumented event if traceId is present. + return contextData; + } + + SpanContext currentContext = TracingContextUtils.getCurrentSpan().getContext(); + if (!currentContext.isValid()) { + return contextData; + } + + StringMap newContextData = new SortedArrayStringMap(contextData); + newContextData.putValue(TRACE_ID, currentContext.getTraceIdAsHexString()); + newContextData.putValue(SPAN_ID, currentContext.getSpanIdAsHexString()); + if (currentContext.isSampled()) { + newContextData.putValue(SAMPLED, "true"); + } + return newContextData; + } + + @Override + public ReadOnlyStringMap rawContextData() { + return delegate.rawContextData(); + } +} diff --git a/instrumentation/log4j/log4j-2.7/src/test/groovy/Log4j27Test.groovy b/instrumentation/log4j/log4j-2.7/src/test/groovy/Log4j27Test.groovy new file mode 100644 index 000000000000..71ee2f8db73f --- /dev/null +++ b/instrumentation/log4j/log4j-2.7/src/test/groovy/Log4j27Test.groovy @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import io.opentelemetry.auto.test.AgentTestTrait + +class Log4j27Test extends Log4j2Test implements AgentTestTrait { +} diff --git a/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LoggingEventInstrumentation.java b/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LoggingEventInstrumentation.java index 2a1d982b4874..a9bfd905abca 100644 --- a/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LoggingEventInstrumentation.java +++ b/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LoggingEventInstrumentation.java @@ -16,6 +16,9 @@ package io.opentelemetry.instrumentation.auto.logback.v1_0_0; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SAMPLED; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SPAN_ID; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; @@ -78,7 +81,7 @@ public static class GetMdcAdvice { public static void onExit( @Advice.This ILoggingEvent event, @Advice.Return(typing = Typing.DYNAMIC, readOnly = false) Map contextData) { - if (contextData != null && contextData.containsKey("traceId")) { + if (contextData != null && contextData.containsKey(TRACE_ID)) { // Assume already instrumented event if traceId is present. return; } @@ -90,10 +93,10 @@ public static void onExit( Map spanContextData = new HashMap<>(); SpanContext spanContext = currentSpan.getContext(); - spanContextData.put("traceId", spanContext.getTraceIdAsHexString()); - spanContextData.put("spanId", spanContext.getSpanIdAsHexString()); + spanContextData.put(TRACE_ID, spanContext.getTraceIdAsHexString()); + spanContextData.put(SPAN_ID, spanContext.getSpanIdAsHexString()); if (spanContext.isSampled()) { - spanContextData.put("sampled", "true"); + spanContextData.put(SAMPLED, "true"); } if (contextData == null) { diff --git a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java index 16bee6353dd3..a2064a1f5a59 100644 --- a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java +++ b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java @@ -16,6 +16,10 @@ package io.opentelemetry.instrumentation.logback.v1_0_0; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SAMPLED; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.SPAN_ID; +import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; + import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.UnsynchronizedAppenderBase; @@ -41,17 +45,17 @@ public static ILoggingEvent wrapEvent(ILoggingEvent event) { } Map eventContext = event.getMDCPropertyMap(); - if (eventContext != null && eventContext.containsKey("traceId")) { + if (eventContext != null && eventContext.containsKey(TRACE_ID)) { // Assume already instrumented event if traceId is present. return event; } Map contextData = new HashMap<>(); SpanContext spanContext = currentSpan.getContext(); - contextData.put("traceId", spanContext.getTraceIdAsHexString()); - contextData.put("spanId", spanContext.getSpanIdAsHexString()); + contextData.put(TRACE_ID, spanContext.getTraceIdAsHexString()); + contextData.put(SPAN_ID, spanContext.getSpanIdAsHexString()); if (spanContext.isSampled()) { - contextData.put("sampled", "true"); + contextData.put(SAMPLED, "true"); } if (eventContext == null) { diff --git a/settings.gradle b/settings.gradle index cbeeef92fc8b..3a99f4bb3494 100644 --- a/settings.gradle +++ b/settings.gradle @@ -127,7 +127,10 @@ include ':instrumentation:lettuce:lettuce-4.0' include ':instrumentation:lettuce:lettuce-5.0' include ':instrumentation:lettuce:lettuce-5.1' include ':instrumentation:log4j:log4j-1.2' +include ':instrumentation:log4j:log4j-2.7' +include ':instrumentation:log4j:log4j-2.13.2:auto' include ':instrumentation:log4j:log4j-2.13.2:library' +include ':instrumentation:log4j:log4j-2-testing' include ':instrumentation:logback:logback-1.0.0:auto' include ':instrumentation:logback:logback-1.0.0:library' include ':instrumentation:logback:logback-1.0.0:testing'