From de25e5bb529c6e35fc82e7dfe7dabf9d2f68c7cc Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 19 Jun 2023 22:47:21 -0700 Subject: [PATCH 01/95] Fix logging timestamp (#8761) --- .../log4j/appender/v2_17/Log4jHelper.java | 2 ++ .../instrumentation/log4j/appender/v2_17/Log4j2Test.java | 9 +++++++++ .../v2_17/OpenTelemetryAppenderConfigTestBase.java | 6 +++--- 3 files changed, 14 insertions(+), 3 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 5d0cf18b17e1..6a31624a42bf 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 @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor; import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -66,6 +67,7 @@ public static void capture( .logRecordBuilder(); Map contextData = ThreadContext.getImmutableContext(); mapper.mapLogEvent(builder, message, level, marker, throwable, contextData); + builder.setTimestamp(Instant.now()); builder.emit(); } diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java index 4c753b52c4ee..612d9fce4cf5 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java @@ -8,6 +8,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.logs.Severity; @@ -16,6 +17,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.time.Instant; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -68,6 +70,8 @@ private static void test( String expectedSeverityText) throws InterruptedException { + Instant start = Instant.now(); + // when if (withParent) { testing.runWithSpan( @@ -88,6 +92,11 @@ private static void test( .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build()) .hasSeverity(expectedSeverity) .hasSeverityText(expectedSeverityText); + + assertThat(log.getTimestampEpochNanos()) + .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + if (logException) { assertThat(log) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java index f35564694fcb..535937e5fc3f 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java @@ -9,6 +9,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; @@ -24,7 +25,6 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.time.Instant; import java.util.List; -import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -107,8 +107,8 @@ void logWithExtras() { satisfies(SemanticAttributes.EXCEPTION_STACKTRACE, v -> v.contains("logWithExtras"))); assertThat(logDataList.get(0).getTimestampEpochNanos()) - .isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli())) - .isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); } @Test From 1618303b65850a82b5b2a6fc654bfdc8b2ed59f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 08:47:42 +0300 Subject: [PATCH 02/95] Bump org.mockito:mockito-core from 5.3.1 to 5.4.0 in /examples/distro (#8764) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/distro/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index 65c47d5ad219..abb4423d7b36 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -70,7 +70,7 @@ subprojects { implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:${versions.opentelemetryJavaagent}")) implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${versions.opentelemetryJavaagentAlpha}")) - testImplementation("org.mockito:mockito-core:5.3.1") + testImplementation("org.mockito:mockito-core:5.4.0") testImplementation(enforcedPlatform("org.junit:junit-bom:${versions.junit}")) testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}") From b14d12a6eccfaa250b4e57ba27f8322dd906b414 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 08:48:07 +0300 Subject: [PATCH 03/95] Bump com.gradle.enterprise:com.gradle.enterprise.gradle.plugin from 3.13.3 to 3.13.4 (#8762) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- conventions/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index aebd8e1b190c..585ae4cd0c18 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") - implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.3") + implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") implementation("org.owasp:dependency-check-gradle:8.2.1") implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.0") // When updating, also update dependencyManagement/build.gradle.kts From cef9a5fb5c9981d29baad3e43735e5abff3958b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 08:48:31 +0300 Subject: [PATCH 04/95] Bump com.gradle.enterprise from 3.13.3 to 3.13.4 (#8763) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index f93c66b24642..e376d02ed66e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,7 @@ pluginManagement { } plugins { - id("com.gradle.enterprise") version "3.13.3" + id("com.gradle.enterprise") version "3.13.4" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11" id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } From 419fc0f9dd1dbd21de038165fd8aa5dbb3c84d94 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 21 Jun 2023 11:30:24 +0200 Subject: [PATCH 05/95] Add one missing `ClientAttributesExtractor` test (#8774) --- ...ributesExtractorInetSocketAddressTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java new file mode 100644 index 000000000000..2d233f493c21 --- /dev/null +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import java.net.InetSocketAddress; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; + +class ClientAttributesExtractorInetSocketAddressTest { + + static class TestClientAttributesGetter + implements ClientAttributesGetter { + + @Nullable + @Override + public String getClientAddress(InetSocketAddress request) { + // covered in ClientAttributesExtractorTest + return null; + } + + @Nullable + @Override + public Integer getClientPort(InetSocketAddress request) { + // covered in ClientAttributesExtractorTest + return null; + } + + @Nullable + @Override + public InetSocketAddress getClientInetSocketAddress( + InetSocketAddress request, @Nullable Void response) { + return request; + } + } + + @Test + void fullAddress() { + InetSocketAddress address = new InetSocketAddress("api.github.com", 456); + assertThat(address.getAddress().getHostAddress()).isNotNull(); + + AttributesExtractor extractor = + ClientAttributesExtractor.create(new TestClientAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), address); + assertThat(startAttributes.build()).isEmpty(); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), address, null, null); + assertThat(endAttributes.build()) + .containsOnly( + entry(NetworkAttributes.CLIENT_SOCKET_ADDRESS, address.getAddress().getHostAddress()), + entry(NetworkAttributes.CLIENT_SOCKET_PORT, 456L)); + } + + @Test + void noAttributes() { + AttributesExtractor extractor = + ClientAttributesExtractor.create(new TestClientAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), null); + assertThat(startAttributes.build()).isEmpty(); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), null, null, null); + assertThat(endAttributes.build()).isEmpty(); + } +} From 042e95e98f02326170f2758805382f0230f82f17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:31:34 +0300 Subject: [PATCH 06/95] Bump commons-codec:commons-codec from 1.15 to 1.16.0 (#8780) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 878f5750e433..7fc4e2883d0d 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -87,7 +87,7 @@ val DEPENDENCIES = listOf( "com.uber.nullaway:nullaway:0.10.10", "commons-beanutils:commons-beanutils:1.9.4", "commons-cli:commons-cli:1.5.0", - "commons-codec:commons-codec:1.15", + "commons-codec:commons-codec:1.16.0", "commons-collections:commons-collections:3.2.2", "commons-digester:commons-digester:2.1", "commons-fileupload:commons-fileupload:1.5", From 6cf40581cfb4530fcabb54efde9e9dd9e2334c85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:32:00 +0300 Subject: [PATCH 07/95] Bump com.linecorp.armeria:armeria-grpc from 1.24.0 to 1.24.1 (#8781) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/images/fake-backend/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/images/fake-backend/build.gradle.kts b/smoke-tests/images/fake-backend/build.gradle.kts index 5baad1474ef0..0cb6d572d99a 100644 --- a/smoke-tests/images/fake-backend/build.gradle.kts +++ b/smoke-tests/images/fake-backend/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-grpc:1.24.0") + implementation("com.linecorp.armeria:armeria-grpc:1.24.1") implementation("io.opentelemetry.proto:opentelemetry-proto") runtimeOnly("org.slf4j:slf4j-simple") } From a4fb3a51ccf27b5ea17255381fde21a333edea83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:32:27 +0300 Subject: [PATCH 08/95] Bump com.linecorp.armeria:armeria-junit5 from 1.24.0 to 1.24.1 (#8782) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/armeria-shaded-for-testing/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/armeria-shaded-for-testing/build.gradle.kts b/testing/armeria-shaded-for-testing/build.gradle.kts index 481fd608e578..f34e1717a668 100644 --- a/testing/armeria-shaded-for-testing/build.gradle.kts +++ b/testing/armeria-shaded-for-testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-junit5:1.24.0") + implementation("com.linecorp.armeria:armeria-junit5:1.24.1") } tasks { From acd1c92173ebcd1489ee25f490bc107c0a3aa6bb Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 22 Jun 2023 12:44:30 +0300 Subject: [PATCH 09/95] Fix mongo latest dep tests (#8785) --- .../v4_0/BaseClusterInstrumentation.java | 18 ++++++++ ...aultConnectionPoolTaskInstrumentation.java | 43 +++++++++++++++++++ .../MongoClientInstrumentationModule.java | 1 + .../mongo/v4_0/TaskWrapper.java | 27 ++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java create mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java index 0eff79c30dd2..24994d4d1972 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java @@ -34,6 +34,14 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) .and(takesArgument(1, named("com.mongodb.internal.async.SingleResultCallback"))), this.getClass().getName() + "$SingleResultCallbackArg1Advice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("selectServerAsync")) + .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) + .and(takesArgument(2, named("com.mongodb.internal.async.SingleResultCallback"))), + this.getClass().getName() + "$SingleResultCallbackArg2Advice"); } @SuppressWarnings("unused") @@ -45,4 +53,14 @@ public static void wrapCallback( callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); } } + + @SuppressWarnings("unused") + public static class SingleResultCallbackArg2Advice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapCallback( + @Advice.Argument(value = 2, readOnly = false) SingleResultCallback callback) { + callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); + } + } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java new file mode 100644 index 000000000000..a87070f926eb --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.function.Consumer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class DefaultConnectionPoolTaskInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("com.mongodb.internal.connection.DefaultConnectionPool$Task"); + } + + @Override + public void transform(TypeTransformer transformer) { + // outer class this is passed as arg 0 to constructor + transformer.applyAdviceToMethod( + isConstructor().and(takesArgument(2, Consumer.class)), + this.getClass().getName() + "$TaskAdvice"); + } + + @SuppressWarnings("unused") + public static class TaskAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapCallback( + @Advice.Argument(value = 2, readOnly = false) Consumer action) { + action = new TaskWrapper(Java8BytecodeBridge.currentContext(), action); + } + } +} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java index ecc578f7bced..25b8abe5c0c2 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java @@ -33,6 +33,7 @@ public List typeInstrumentations() { new InternalStreamConnectionInstrumentation(), new BaseClusterInstrumentation(), new DefaultConnectionPoolInstrumentation(), + new DefaultConnectionPoolTaskInstrumentation(), new AsyncWorkManagerInstrumentation()); } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java new file mode 100644 index 000000000000..ea4f72229143 --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.function.Consumer; + +public class TaskWrapper implements Consumer { + private final Context context; + private final Consumer delegate; + + public TaskWrapper(Context context, Consumer delegate) { + this.context = context; + this.delegate = delegate; + } + + @Override + public void accept(Object value) { + try (Scope ignored = context.makeCurrent()) { + delegate.accept(value); + } + } +} From 98c1a864bb49a31cc0b6acbe853f5130cbca7f15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:22:10 +0200 Subject: [PATCH 10/95] Bump org.springframework.boot:spring-boot-starter-web from 3.1.0 to 3.1.1 (#8794) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- benchmark-overhead-jmh/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark-overhead-jmh/build.gradle.kts b/benchmark-overhead-jmh/build.gradle.kts index 5f78b9846b55..cb555af356dc 100644 --- a/benchmark-overhead-jmh/build.gradle.kts +++ b/benchmark-overhead-jmh/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } dependencies { - jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.0") + jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.1") } tasks { From 73aeb51329eccfc3f540c55940d93faf6d92b4ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:23:27 +0200 Subject: [PATCH 11/95] Bump org.owasp:dependency-check-gradle from 8.2.1 to 8.3.1 (#8792) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- conventions/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index 585ae4cd0c18..17dc508103e7 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") - implementation("org.owasp:dependency-check-gradle:8.2.1") + implementation("org.owasp:dependency-check-gradle:8.3.1") implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.0") // When updating, also update dependencyManagement/build.gradle.kts implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.14.5") From 09d3ac55d23f3e42d00864c465ecb00a2dc7eb52 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 23 Jun 2023 15:08:08 +0200 Subject: [PATCH 12/95] [minor cleanup] remove shell output from cgroup examples (#8797) --- .../library/src/test/resources/docker_proc_self_mountinfo | 2 -- .../library/src/test/resources/podman_proc_self_mountinfo | 2 -- 2 files changed, 4 deletions(-) diff --git a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo index d707c05184d9..28af893acf71 100644 --- a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo @@ -1,5 +1,3 @@ -473 456 254:1 /docker/containers/be522444b60caf2d3934b8b24b916a8a314f4b68d4595aa419874657e8d103f2/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw -root@be522444b60c:/# cat /proc/self/mountinfo 456 375 0:143 / / rw,relatime master:175 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CBPR2ETR4Z3UMOOGIIRDVT2P27:/var/lib/docker/overlay2/l/46FCA2JFPCSNFGAR5TSYLLNHLK,upperdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/diff,workdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/work 457 456 0:146 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 466 456 0:147 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 diff --git a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo index 5d67bcdd1a60..6fb20214fc5d 100644 --- a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo @@ -1,5 +1,3 @@ -983 961 0:56 /containers/overlay-containers/2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6/userdata/hostname /etc/hostname ro,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=783888k,nr_inodes=195972,mode=700,uid=2024,gid=2024,inode64 -[root@2a33efc76e51 /]# cat /proc/self/mountinfo 961 812 0:58 / / ro,relatime - overlay overlay rw,lowerdir=/home/dracula/.local/share/containers/storage/overlay/l/4NB35A5Z4YGWDHXYEUZU4FN6BU,upperdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/diff,workdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/work,userxattr 962 961 0:63 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs rw 963 961 0:64 / /run rw,nosuid,nodev,relatime - tmpfs tmpfs rw,uid=2024,gid=2024,inode64 From 3733094d5c1b4f15cd6102b0e9385ee5e1405fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 23 Jun 2023 15:11:55 +0200 Subject: [PATCH 13/95] aws-sdk-2.2: More reflection cleanup. (#8775) Co-authored-by: Mateusz Rzeszutek --- .../aws-sdk-2.2/javaagent/build.gradle.kts | 49 +++++-- .../AbstractAwsSdkInstrumentationModule.java | 39 ++++++ .../v2_2/AwsSdkInstrumentationModule.java | 35 +---- .../awssdk/v2_2/SqsInstrumentationModule.java | 28 +--- .../aws-sdk-2.2/library/build.gradle.kts | 1 + .../awssdk/v2_2/SqsAccess.java | 44 +++++-- .../awssdk/v2_2/SqsAdviceBridge.java | 6 +- .../instrumentation/awssdk/v2_2/SqsImpl.java | 51 +++++++- .../v2_2/SqsReceiveMessageRequestAccess.java | 122 ------------------ .../v2_2/SqsSendMessageRequestAccess.java | 23 ---- .../v2_2/TracingExecutionInterceptor.java | 50 +------ .../awssdk/v2_2/Aws2ClientTest.groovy | 2 +- .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 39 ++++++ .../v2_2/AbstractAws2SqsTracingTest.groovy | 13 +- 14 files changed, 216 insertions(+), 286 deletions(-) delete mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java delete mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index 4def26b6629d..c33b3b1fa314 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -15,9 +15,23 @@ muzzle { // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") } -} -muzzle { + fail { + group.set("software.amazon.awssdk") + module.set("aws-core") + versions.set("[2.2.0,)") + // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP + // client, which is not target of instrumentation anyways. + extraDependency("software.amazon.awssdk:protocol-core") + + // "fail" asserts that *all* the instrumentation modules fail to load, but the core one is + // actually expected to succeed, so exclude it from checks. + excludeInstrumentationName("aws-sdk-2.2-core") + + // several software.amazon.awssdk artifacts are missing for this version + skip("2.17.200") + } + pass { group.set("software.amazon.awssdk") module.set("sqs") @@ -53,17 +67,26 @@ dependencies { latestDepTestLibrary("software.amazon.awssdk:sqs:+") } -tasks.withType().configureEach { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) - // TODO run tests both with and without experimental span attributes, with & without extra propagation - systemProperties(mapOf( - "otel.instrumentation.aws-sdk.experimental-span-attributes" to "true", - "otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging" to "true", - )) -} +tasks { + val testExperimentalSqs by registering(Test::class) { + group = "verification" + + systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", "true") + } + + check { + dependsOn(testExperimentalSqs) + } + + withType().configureEach { + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + // TODO run tests both with and without experimental span attributes + systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", "true") + } -tasks.withType().configureEach { - mergeServiceFiles { - include("software/amazon/awssdk/global/handlers/execution.interceptors") + withType().configureEach { + mergeServiceFiles { + include("software/amazon/awssdk/global/handlers/execution.interceptors") + } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java index e3cc3579cd3c..25b7b5c06c38 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java @@ -5,7 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.named; + import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; abstract class AbstractAwsSdkInstrumentationModule extends InstrumentationModule { @@ -17,4 +26,34 @@ protected AbstractAwsSdkInstrumentationModule(String additionalInstrumentationNa public boolean isHelperClass(String className) { return className.startsWith("io.opentelemetry.contrib.awsxray."); } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // We don't actually transform it but want to make sure we only apply the instrumentation when + // our key dependency is present. + return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ResourceInjectingTypeInstrumentation()); + } + + abstract void doTransform(TypeTransformer transformer); + + // A type instrumentation is needed to trigger resource injection. + public class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure + // our interceptor service definition is injected as early as possible if we typematch against + // it. + return named("software.amazon.awssdk.core.SdkClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + doTransform(transformer); + } + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java index 8ee1719a7c2e..a6bb33a49b77 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java @@ -5,19 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.named; - import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor; import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class AwsSdkInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -35,30 +27,7 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) } @Override - public ElementMatcher.Junction classLoaderMatcher() { - // We don't actually transform it but want to make sure we only apply the instrumentation when - // our key dependency is present. - return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); - } - - @Override - public List typeInstrumentations() { - return singletonList(new ResourceInjectingTypeInstrumentation()); - } - - // A type instrumentation is needed to trigger resource injection. - public static class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure - // our interceptor service definition is injected as early as possible if we typematch against - // it. - return named("software.amazon.awssdk.core.SdkClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - // Nothing to transform, this type instrumentation is only used for injecting resources. - } + void doTransform(TypeTransformer transformer) { + // Nothing to transform, this type instrumentation is only used for injecting resources. } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java index 380443ab96a1..3fdab6da5d2f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java @@ -5,19 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.none; import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.SqsAdviceBridge; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class SqsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -27,21 +21,9 @@ public SqsInstrumentationModule() { } @Override - public List typeInstrumentations() { - return singletonList(new DefaultSqsClientTypeInstrumentation()); - } - - public static class DefaultSqsClientTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - return named("software.amazon.awssdk.services.sqs.DefaultSqsClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isConstructor(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); - } + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); } @SuppressWarnings("unused") @@ -50,7 +32,7 @@ public static class RegisterAdvice { public static void onExit() { // (indirectly) using SqsImpl class here to make sure it is available from SqsAccess // (injected into app classloader) and checked by Muzzle - SqsAdviceBridge.init(); + SqsAdviceBridge.referenceForMuzzleOnly(); } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 56f43c7e1668..8ff9545dcb61 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -26,5 +26,6 @@ tasks { test { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) + systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java index 6efff826aa29..f1f8aad68dd1 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java @@ -8,12 +8,18 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; +import software.amazon.awssdk.services.sqs.model.SendMessageRequest; // helper class for calling methods that use sqs types in SqsImpl // if SqsImpl is not present these methods are no op final class SqsAccess { + private SqsAccess() {} + private static final boolean enabled = isSqsImplPresent(); private static boolean isSqsImplPresent() { @@ -31,14 +37,34 @@ private static boolean isSqsImplPresent() { } @NoMuzzle - static SdkRequest injectIntoSqsSendMessageRequest( + static boolean isSendMessageRequest(SdkRequest request) { + return enabled && request instanceof SendMessageRequest; + } + + @NoMuzzle + static SdkRequest injectIntoSendMessageRequest( TextMapPropagator messagingPropagator, SdkRequest rawRequest, io.opentelemetry.context.Context otelContext) { - if (!enabled) { - return rawRequest; - } - return SqsImpl.injectIntoSqsSendMessageRequest(messagingPropagator, rawRequest, otelContext); + assert enabled; // enabled checked already in instance check. + return SqsImpl.injectIntoSendMessageRequest(messagingPropagator, rawRequest, otelContext); + } + + @NoMuzzle + static boolean isReceiveMessageRequest(SdkRequest request) { + return enabled && request instanceof ReceiveMessageRequest; + } + + @NoMuzzle + public static SdkRequest modifyReceiveMessageRequest( + SdkRequest request, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { + assert enabled; // enabled checked already in instance check. + return SqsImpl.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); + } + + @NoMuzzle + static boolean isReceiveMessageResponse(SdkResponse response) { + return enabled && response instanceof ReceiveMessageResponse; } @NoMuzzle @@ -46,11 +72,7 @@ static void afterReceiveMessageExecution( TracingExecutionInterceptor config, Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (!enabled) { - return; - } - SqsImpl.afterConsumerResponse(config, executionAttributes, context); + assert enabled; // enabled checked already in instance check. + SqsImpl.afterReceiveMessageExecution(config, executionAttributes, context); } - - private SqsAccess() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java index 20eebbeac8e2..ddb3c05c5cbb 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java @@ -8,8 +8,8 @@ public final class SqsAdviceBridge { private SqsAdviceBridge() {} - public static void init() { - // called from advice - SqsImpl.init(); // Reference the actual, package-private, implementation class for Muzzle + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SqsImpl.class.getName() + " referencing for muzzle, should never be actually called"); } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java index 99c1f155450e..df3b402206f8 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java @@ -7,7 +7,9 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.interceptor.Context; @@ -15,6 +17,7 @@ import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; @@ -22,11 +25,7 @@ final class SqsImpl { private SqsImpl() {} - public static void init() { - // called from advice - } - - static SdkRequest injectIntoSqsSendMessageRequest( + static SdkRequest injectIntoSendMessageRequest( TextMapPropagator messagingPropagator, SdkRequest rawRequest, io.opentelemetry.context.Context otelContext) { @@ -48,7 +47,7 @@ static SdkRequest injectIntoSqsSendMessageRequest( } /** Create and close CONSUMER span for each message consumed. */ - static void afterConsumerResponse( + static void afterReceiveMessageExecution( TracingExecutionInterceptor config, ExecutionAttributes executionAttributes, Context.AfterExecution context) { @@ -91,4 +90,44 @@ private static void createConsumerSpan( consumerInstrumenter.end(context, executionAttributes, httpResponse, null); } } + + static SdkRequest modifyReceiveMessageRequest( + SdkRequest rawRequest, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { + ReceiveMessageRequest request = (ReceiveMessageRequest) rawRequest; + boolean hasXrayAttribute = true; + List existingAttributeNames = null; + if (useXrayPropagator) { + existingAttributeNames = request.attributeNamesAsStrings(); + hasXrayAttribute = + existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + + boolean hasMessageAttribute = true; + List existingMessageAttributeNames = null; + if (messagingPropagator != null) { + existingMessageAttributeNames = request.messageAttributeNames(); + hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); + } + + if (hasMessageAttribute && hasXrayAttribute) { + return request; + } + + ReceiveMessageRequest.Builder builder = request.toBuilder(); + if (!hasXrayAttribute) { + List attributeNames = new ArrayList<>(existingAttributeNames); + attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + builder.attributeNamesWithStrings(attributeNames); + } + if (messagingPropagator != null) { + List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); + for (String field : messagingPropagator.fields()) { + if (!existingMessageAttributeNames.contains(field)) { + messageAttributeNames.add(field); + } + } + builder.messageAttributeNames(messageAttributeNames); + } + return builder.build(); + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java deleted file mode 100644 index b6ac23ded0fe..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nullable; -import software.amazon.awssdk.core.SdkRequest; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - * - * @see SDK - * Javadoc - * @see Definition - * JSON - */ -final class SqsReceiveMessageRequestAccess { - - @Nullable private static final MethodHandle ATTRIBUTE_NAMES_WITH_STRINGS; - @Nullable private static final MethodHandle MESSAGE_ATTRIBUTE_NAMES; - - static { - Class receiveMessageRequestClass = null; - try { - receiveMessageRequestClass = - Class.forName("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest$Builder"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageRequestClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle withAttributeNames = null; - try { - withAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "attributeNamesWithStrings", - methodType(receiveMessageRequestClass, Collection.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - ATTRIBUTE_NAMES_WITH_STRINGS = withAttributeNames; - - MethodHandle messageAttributeNames = null; - try { - messageAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "messageAttributeNames", - methodType(receiveMessageRequestClass, Collection.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - MESSAGE_ATTRIBUTE_NAMES = messageAttributeNames; - } else { - ATTRIBUTE_NAMES_WITH_STRINGS = null; - MESSAGE_ATTRIBUTE_NAMES = null; - } - } - - static boolean isInstance(SdkRequest request) { - return request - .getClass() - .getName() - .equals("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest"); - } - - static void attributeNamesWithStrings(SdkRequest.Builder builder, List attributeNames) { - if (ATTRIBUTE_NAMES_WITH_STRINGS == null) { - return; - } - try { - ATTRIBUTE_NAMES_WITH_STRINGS.invoke(builder, attributeNames); - } catch (Throwable throwable) { - // Ignore - } - } - - static void messageAttributeNames( - SdkRequest.Builder builder, List messageAttributeNames) { - if (MESSAGE_ATTRIBUTE_NAMES == null) { - return; - } - try { - MESSAGE_ATTRIBUTE_NAMES.invoke(builder, messageAttributeNames); - } catch (Throwable throwable) { - // Ignore - } - } - - private SqsReceiveMessageRequestAccess() {} - - @SuppressWarnings({"rawtypes", "unchecked"}) - static List getAttributeNames(SdkRequest request) { - Optional optional = request.getValueForField("AttributeNames", List.class); - return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - static List getMessageAttributeNames(SdkRequest request) { - Optional optional = request.getValueForField("MessageAttributeNames", List.class); - return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); - } -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java deleted file mode 100644 index b863ef3ceaf2..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import software.amazon.awssdk.core.SdkRequest; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest for points where we are not - * sure whether SQS is on the classpath. - */ -final class SqsSendMessageRequestAccess { - static boolean isInstance(SdkRequest request) { - return request - .getClass() - .getName() - .equals("software.amazon.awssdk.services.sqs.model.SendMessageRequest"); - } - - private SqsSendMessageRequestAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index 95d81cd3034d..ccdc223ec5fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -15,7 +15,6 @@ import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import software.amazon.awssdk.awscore.AwsResponse; @@ -122,56 +121,17 @@ public SdkRequest modifyRequest( throw throwable; } - if (SqsReceiveMessageRequestAccess.isInstance(request)) { - return modifySqsReceiveMessageRequest(request); + if (SqsAccess.isReceiveMessageRequest(request)) { + return SqsAccess.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); } else if (messagingPropagator != null) { - if (SqsSendMessageRequestAccess.isInstance(request)) { - return SqsAccess.injectIntoSqsSendMessageRequest(messagingPropagator, request, otelContext); + if (SqsAccess.isSendMessageRequest(request)) { + return SqsAccess.injectIntoSendMessageRequest(messagingPropagator, request, otelContext); } // TODO: Support SendMessageBatchRequest (and thus SendMessageBatchRequestEntry) } return request; } - private SdkRequest modifySqsReceiveMessageRequest(SdkRequest request) { - boolean hasXrayAttribute = true; - List existingAttributeNames = null; - if (useXrayPropagator) { - existingAttributeNames = SqsReceiveMessageRequestAccess.getAttributeNames(request); - hasXrayAttribute = - existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - } - - boolean hasMessageAttribute = true; - List existingMessageAttributeNames = null; - if (messagingPropagator != null) { - existingMessageAttributeNames = - SqsReceiveMessageRequestAccess.getMessageAttributeNames(request); - hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); - } - - if (hasMessageAttribute && hasXrayAttribute) { - return request; - } - - SdkRequest.Builder builder = request.toBuilder(); - if (!hasXrayAttribute) { - List attributeNames = new ArrayList<>(existingAttributeNames); - attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - SqsReceiveMessageRequestAccess.attributeNamesWithStrings(builder, attributeNames); - } - if (messagingPropagator != null) { - List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); - for (String field : messagingPropagator.fields()) { - if (!existingMessageAttributeNames.contains(field)) { - messageAttributeNames.add(field); - } - } - SqsReceiveMessageRequestAccess.messageAttributeNames(builder, messageAttributeNames); - } - return builder.build(); - } - @Override public void afterMarshalling( Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { @@ -265,7 +225,7 @@ private void populateRequestAttributes( @Override public void afterExecution( Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (SqsReceiveMessageRequestAccess.isInstance(context.request())) { + if (SqsAccess.isReceiveMessageResponse(context.response())) { SqsAccess.afterReceiveMessageExecution(this, context, executionAttributes); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy index 211773859068..40a88e4c5863 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy @@ -15,7 +15,7 @@ class Aws2ClientTest extends AbstractAws2ClientTest implements LibraryTestTrait .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Default on in tests to cover more code + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) .build() .newExecutionInterceptor()) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index a3fcdca522da..3a10459e6dea 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -5,12 +5,14 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil import io.opentelemetry.instrumentation.test.InstrumentationSpecification import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import io.opentelemetry.testing.internal.armeria.common.HttpResponse import io.opentelemetry.testing.internal.armeria.common.HttpStatus import io.opentelemetry.testing.internal.armeria.common.MediaType import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension +import org.junit.jupiter.api.Assumptions import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.core.ResponseInputStream @@ -48,6 +50,7 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest +import software.amazon.awssdk.services.sqs.model.SendMessageRequest import spock.lang.Shared import spock.lang.Unroll @@ -317,7 +320,22 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { return AttributeValue.builder().s(value).build() } + def isSqsAttributeInjectionEnabled() { + // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor + return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) + } + + void assumeSupportedConfig(service, operation) { + Assumptions.assumeFalse( + service == "Sqs" + && operation == "SendMessage" + && isSqsAttributeInjectionEnabled(), + "Cannot check Sqs.SendMessage here due to hard-coded MD5.") + } + def "send #operation request with builder #builder.class.getName() mocked response"() { + assumeSupportedConfig(service, operation) + setup: configureSdkClient(builder) def client = builder @@ -384,6 +402,16 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ + "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE @@ -399,6 +427,7 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { } def "send #operation async request with builder #builder.class.getName() mocked response"() { + assumeSupportedConfig(service, operation) setup: configureSdkClient(builder) def client = builder @@ -465,6 +494,16 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ + "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index 7a7a96efefb0..a86c9bc8a81a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 import io.opentelemetry.instrumentation.test.InstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import org.elasticmq.rest.sqs.SQSRestServerBuilder import software.amazon.awssdk.auth.credentials.AwsBasicCredentials @@ -61,10 +60,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() def setupSpec() { - sqsPort = PortUtils.findOpenPort() - sqs = SQSRestServerBuilder.withPort(sqsPort).withInterface("localhost").start() + sqs = SQSRestServerBuilder.withPort(0).withInterface("localhost").start() + def server = sqs.waitUntilStarted() + sqsPort = server.localAddress().port println getClass().name + " SQS server started at: localhost:$sqsPort/" - } def cleanupSpec() { @@ -181,9 +180,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest) - client.receiveMessage(receiveMessageRequest) + def resp = client.receiveMessage(receiveMessageRequest) then: + resp.messages().size() == 1 assertSqsTraces() } @@ -198,9 +198,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest).get() - client.receiveMessage(receiveMessageRequest).get() + def resp = client.receiveMessage(receiveMessageRequest).get() then: + resp.messages().size() == 1 assertSqsTraces() } } From d38a00633accd60c8241cd7686985b5f46d6ba46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 22:11:14 +0300 Subject: [PATCH 14/95] Bump com.uber.nullaway:nullaway from 0.10.10 to 0.10.11 (#8801) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7fc4e2883d0d..c2eb71beb3f5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -84,7 +84,7 @@ val DEPENDENCIES = listOf( "com.github.stefanbirkner:system-lambda:1.2.1", "com.github.stefanbirkner:system-rules:1.19.0", "uk.org.webcompere:system-stubs-jupiter:2.0.2", - "com.uber.nullaway:nullaway:0.10.10", + "com.uber.nullaway:nullaway:0.10.11", "commons-beanutils:commons-beanutils:1.9.4", "commons-cli:commons-cli:1.5.0", "commons-codec:commons-codec:1.16.0", From f197befb508cb700b197aeb9915760ed4de1a028 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Mon, 26 Jun 2023 12:34:09 +0200 Subject: [PATCH 15/95] Minor fixes to the `server.*` attributes extrator (#8772) --- .../InternalServerAttributesExtractor.java | 63 +++++++++---------- ...tAddressNetClientAttributesGetterTest.java | 6 +- ...tAddressNetServerAttributesGetterTest.java | 4 +- .../net/NetClientAttributesExtractorTest.java | 9 ++- .../net/NetServerAttributesExtractorTest.java | 5 +- .../ServerAttributesExtractorTest.java | 25 +++++++- .../apachedubbo/v2_7/AbstractDubboTest.groovy | 2 + .../v2_7/AbstractDubboTraceChainTest.groovy | 3 + .../client/Vertx3NetAttributesGetter.java | 23 ------- .../groovy/client/VertxHttpClientTest.groovy | 3 +- 10 files changed, 77 insertions(+), 66 deletions(-) diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java index 68e4ef1db9ce..3796801f9e61 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java @@ -45,25 +45,22 @@ public InternalServerAttributesExtractor( public void onStart(AttributesBuilder attributes, REQUEST request) { String serverAddress = extractServerAddress(request); + if (emitStableUrlAttributes) { + internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, serverAddress); + } + if (emitOldHttpAttributes) { + internalSet(attributes, oldSemconvMode.address, serverAddress); + } - if (serverAddress != null) { + Integer serverPort = extractServerPort(request); + if (serverPort != null + && serverPort > 0 + && captureServerPortCondition.test(serverPort, request)) { if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, serverAddress); + internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) serverPort); } if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.address, serverAddress); - } - - Integer serverPort = extractServerPort(request); - if (serverPort != null - && serverPort > 0 - && captureServerPortCondition.test(serverPort, request)) { - if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) serverPort); - } - if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.port, (long) serverPort); - } + internalSet(attributes, oldSemconvMode.port, (long) serverPort); } } } @@ -79,28 +76,26 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO if (emitOldHttpAttributes) { internalSet(attributes, oldSemconvMode.socketAddress, serverSocketAddress); } + } - Integer serverPort = extractServerPort(request); - Integer serverSocketPort = getter.getServerSocketPort(request, response); - if (serverSocketPort != null - && serverSocketPort > 0 - && !serverSocketPort.equals(serverPort)) { - if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); - } - if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.socketPort, (long) serverSocketPort); - } + Integer serverPort = extractServerPort(request); + Integer serverSocketPort = getter.getServerSocketPort(request, response); + if (serverSocketPort != null && serverSocketPort > 0 && !serverSocketPort.equals(serverPort)) { + if (emitStableUrlAttributes) { + internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); } + if (emitOldHttpAttributes) { + internalSet(attributes, oldSemconvMode.socketPort, (long) serverSocketPort); + } + } - String serverSocketDomain = getter.getServerSocketDomain(request, response); - if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddress)) { - if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); - } - if (emitOldHttpAttributes && oldSemconvMode.socketDomain != null) { - internalSet(attributes, oldSemconvMode.socketDomain, serverSocketDomain); - } + String serverSocketDomain = getter.getServerSocketDomain(request, response); + if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddress)) { + if (emitStableUrlAttributes) { + internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); + } + if (emitOldHttpAttributes && oldSemconvMode.socketDomain != null) { + internalSet(attributes, oldSemconvMode.socketDomain, serverSocketDomain); } } } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java index 03fe8a25e4ff..459c3c183aaa 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.instrumenter.net; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -103,6 +104,9 @@ void unresolved() { // then assertThat(startAttributes.build()).isEmpty(); - assertThat(endAttributes.build()).isEmpty(); + assertThat(endAttributes.build()) + .containsOnly( + entry(SemanticAttributes.NET_SOCK_PEER_NAME, "api.github.com"), + entry(SemanticAttributes.NET_SOCK_PEER_PORT, 456L)); } } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java index 60943f038ebf..540fcdba4aba 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java @@ -117,7 +117,9 @@ void unresolved() { assertThat(startAttributes.build()).isEmpty(); assertThat(endAttributes.build()) - .containsOnly(entry(SemanticAttributes.NET_SOCK_PEER_PORT, 123L)); + .containsOnly( + entry(SemanticAttributes.NET_SOCK_PEER_PORT, 123L), + entry(SemanticAttributes.NET_SOCK_HOST_PORT, 456L)); } static final class Addresses { diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java index 84cc2e27cf10..3278a22270d0 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java @@ -157,7 +157,8 @@ void empty() { } @Test - @DisplayName("does not set any net.sock.* attributes when net.peer.name = net.sock.peer.addr") + @DisplayName( + "does not set those net.sock.peer.* attributes that duplicate corresponding net.peer.* attributes") void doesNotSetDuplicates1() { // given Map map = new HashMap<>(); @@ -184,7 +185,11 @@ void doesNotSetDuplicates1() { entry(SemanticAttributes.NET_PEER_NAME, "1:2:3:4::"), entry(SemanticAttributes.NET_PEER_PORT, 42L)); - assertThat(endAttributes.build()).containsOnly(entry(SemanticAttributes.NET_TRANSPORT, IP_TCP)); + assertThat(endAttributes.build()) + .containsOnly( + entry(SemanticAttributes.NET_TRANSPORT, IP_TCP), + entry(SemanticAttributes.NET_SOCK_PEER_NAME, "proxy.opentelemetry.io"), + entry(SemanticAttributes.NET_SOCK_PEER_PORT, 123L)); } @Test diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java index 26e726619d74..63239ef701b8 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java @@ -166,7 +166,7 @@ void empty() { @Test @DisplayName( - "does not set any net.sock.host.* attributes when net.host.name = net.sock.host.addr") + "does not set those net.sock.host.* attributes that duplicate corresponding net.host.* attributes") void doesNotSetDuplicates1() { // given Map map = new HashMap<>(); @@ -193,7 +193,8 @@ void doesNotSetDuplicates1() { entry(SemanticAttributes.NET_HOST_NAME, "4:3:2:1::"), entry(SemanticAttributes.NET_HOST_PORT, 80L)); - assertThat(endAttributes.build()).isEmpty(); + assertThat(endAttributes.build()) + .containsOnly(entry(SemanticAttributes.NET_SOCK_HOST_PORT, 8080L)); } @Test diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java index 0d0ed6f7e786..8f2806514c39 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java @@ -121,6 +121,27 @@ void doesNotSetNegativePortValues() { .containsOnly(entry(NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4")); } - // TODO: add more test cases around duplicate data once - // https://github.com/open-telemetry/semantic-conventions/issues/85 clears up + @Test + void doesNotSetDuplicates() { + Map request = new HashMap<>(); + request.put("address", "1.2.3.4"); + request.put("port", "80"); + request.put("socketDomain", "1.2.3.4"); + request.put("socketAddress", "1.2.3.4"); + request.put("socketPort", "80"); + + AttributesExtractor, Void> extractor = + ServerAttributesExtractor.create(new TestServerAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), request); + assertThat(startAttributes.build()) + .containsOnly( + entry(NetworkAttributes.SERVER_ADDRESS, "1.2.3.4"), + entry(NetworkAttributes.SERVER_PORT, 80L)); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), request, null, null); + assertThat(endAttributes.build()).isEmpty(); + } } diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy index e3d7445b22ce..65bedfc1bef7 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy @@ -116,6 +116,7 @@ abstract class AbstractDubboTest extends InstrumentationSpecification { "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } @@ -189,6 +190,7 @@ abstract class AbstractDubboTest extends InstrumentationSpecification { "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy index 8295373562df..c891ba8787ec 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy @@ -152,6 +152,7 @@ abstract class AbstractDubboTraceChainTest extends InstrumentationSpecification "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } @@ -180,6 +181,7 @@ abstract class AbstractDubboTraceChainTest extends InstrumentationSpecification "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } @@ -265,6 +267,7 @@ abstract class AbstractDubboTraceChainTest extends InstrumentationSpecification "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } diff --git a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java index de42ad0ec932..ff5e07c1bf40 100644 --- a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java +++ b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java @@ -8,7 +8,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.net.SocketAddress; import javax.annotation.Nullable; enum Vertx3NetAttributesGetter @@ -25,26 +24,4 @@ public String getServerAddress(HttpClientRequest request) { public Integer getServerPort(HttpClientRequest request) { return null; } - - @Nullable - @Override - public String getServerSocketDomain( - HttpClientRequest request, @Nullable HttpClientResponse response) { - if (response == null) { - return null; - } - SocketAddress socketAddress = response.netSocket().remoteAddress(); - return socketAddress == null ? null : socketAddress.host(); - } - - @Nullable - @Override - public Integer getServerSocketPort( - HttpClientRequest request, @Nullable HttpClientResponse response) { - if (response == null) { - return null; - } - SocketAddress socketAddress = response.netSocket().remoteAddress(); - return socketAddress == null ? null : socketAddress.port(); - } } diff --git a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy index e1174cf52686..2452ccd5130a 100644 --- a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy +++ b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy @@ -19,6 +19,7 @@ import io.vertx.core.http.HttpMethod import spock.lang.Shared import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit import static io.opentelemetry.api.common.AttributeKey.stringKey @@ -54,7 +55,7 @@ class VertxHttpClientTest extends HttpClientTest implements A @Override int sendRequest(HttpClientRequest request, String method, URI uri, Map headers) { // Vertx doesn't seem to provide any synchronous API so bridge through a callback - return sendRequest(request).get() + return sendRequest(request).get(30, TimeUnit.SECONDS) } @Override From a60a324189a6b4fdbde8e00944ef12d9e213c194 Mon Sep 17 00:00:00 2001 From: Stephen Cprek Date: Tue, 27 Jun 2023 06:14:04 -0400 Subject: [PATCH 16/95] Add easier discoverability and clarity to the MDC appender docs (#8638) Co-authored-by: opentelemetrybot <107717825+opentelemetrybot@users.noreply.github.com> --- docs/logger-mdc-instrumentation.md | 32 +++++++++++-------- .../logback/logback-mdc-1.0/library/README.md | 12 +++++-- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/logger-mdc-instrumentation.md b/docs/logger-mdc-instrumentation.md index 34255a3f4317..42aa1ffdbc1b 100644 --- a/docs/logger-mdc-instrumentation.md +++ b/docs/logger-mdc-instrumentation.md @@ -19,21 +19,27 @@ event's MDC copy: (same as `Span.current().getSpanContext().getTraceFlags().asHex()`). Those three pieces of information can be included in log statements produced by the logging library -by specifying them in the pattern/format. +by specifying them in the pattern/format. This way any services or tools that parse the application +logs can correlate traces/spans with log statements. -Tip: for Spring Boot configuration which uses logback, you can add MDC to log lines by overriding only the `logging.pattern.level`: +> Note: If the current `Span` is invalid, the OpenTelemetry appender will not inject any trace information. -```properties -logging.pattern.level = trace_id=%mdc{trace_id} span_id=%mdc{span_id} trace_flags=%mdc{trace_flags} %5p -``` +## Supported logging libraries -This way any services or tools that parse the application logs can correlate traces/spans with log -statements. +> Note: There are also log appenders for exporting logs to OpenTelemetry, not to be confused with the MDC appenders. -## Supported logging libraries +| Library | Auto-instrumented versions | Standalone Library Instrumentation | +|---------|----------------------------|--------------------------------------------------------------------------------------| +| Log4j 1 | 1.2+ | | +| Log4j 2 | 2.7+ | [opentelemetry-log4j-context-data-2.17-autoconfigure](../instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure) | | +| Logback | 1.0+ | [opentelemetry-logback-mdc-1.0](../instrumentation/logback/logback-mdc-1.0/library) | + +## Frameworks -| Library | Version | -|---------|---------| -| Log4j 1 | 1.2+ | -| Log4j 2 | 2.7+ | -| Logback | 1.0+ | +### Spring Boot + +For Spring Boot configuration which uses logback, you can add MDC to log lines by overriding only the `logging.pattern.level`: + +```properties +logging.pattern.level = trace_id=%mdc{trace_id} span_id=%mdc{span_id} trace_flags=%mdc{trace_flags} %5p +``` diff --git a/instrumentation/logback/logback-mdc-1.0/library/README.md b/instrumentation/logback/logback-mdc-1.0/library/README.md index 7dbd4d155672..cd71b6beb50e 100644 --- a/instrumentation/logback/logback-mdc-1.0/library/README.md +++ b/instrumentation/logback/logback-mdc-1.0/library/README.md @@ -32,7 +32,7 @@ dependencies { ### Usage -logback.xml: +The following demonstrates how you might configure the appender in your `logback.xml` configuration: ```xml @@ -47,10 +47,18 @@ logback.xml: - ... + + + + + + ``` +> It's important to note you can also use other encoders in the `ConsoleAppender` like [logstash-logback-encoder](https://github.com/logfellow/logstash-logback-encoder). + This can be helpful when the `Span` is invalid and the `trace_id`, `span_id`, and `trace_flags` are all `null` and are hidden entirely from the logs. + Logging events will automatically have context information from the span context injected. The following attributes are available for use: From f25cd6354c3e7a2036fd4f90d3f313017961c6ff Mon Sep 17 00:00:00 2001 From: Abhinandan Seshadri Date: Wed, 28 Jun 2023 04:51:27 -0400 Subject: [PATCH 17/95] Convert spring rabbitmq tests from groovy to java (#8765) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: dependabot[bot] Co-authored-by: Trask Stalnaker Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mateusz Rzeszutek Co-authored-by: Lauri Tulmin Co-authored-by: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Co-authored-by: Christian Neumüller --- benchmark-overhead-jmh/build.gradle.kts | 2 +- conventions/build.gradle.kts | 4 +- dependencyManagement/build.gradle.kts | 4 +- examples/distro/build.gradle | 2 +- ...ributesExtractorInetSocketAddressTest.java | 80 ------- .../aws-sdk-2.2/javaagent/build.gradle.kts | 49 ++-- .../AbstractAwsSdkInstrumentationModule.java | 39 ---- .../v2_2/AwsSdkInstrumentationModule.java | 35 ++- .../awssdk/v2_2/SqsInstrumentationModule.java | 28 ++- .../aws-sdk-2.2/library/build.gradle.kts | 1 - .../awssdk/v2_2/SqsAccess.java | 44 +--- .../awssdk/v2_2/SqsAdviceBridge.java | 6 +- .../instrumentation/awssdk/v2_2/SqsImpl.java | 51 +---- .../v2_2/SqsReceiveMessageRequestAccess.java | 122 ++++++++++ .../v2_2/SqsSendMessageRequestAccess.java | 23 ++ .../v2_2/TracingExecutionInterceptor.java | 50 +++- .../awssdk/v2_2/Aws2ClientTest.groovy | 2 +- .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 39 ---- .../v2_2/AbstractAws2SqsTracingTest.groovy | 13 +- .../log4j/appender/v2_17/Log4jHelper.java | 2 - .../log4j/appender/v2_17/Log4j2Test.java | 9 - .../OpenTelemetryAppenderConfigTestBase.java | 6 +- .../v4_0/BaseClusterInstrumentation.java | 18 -- ...aultConnectionPoolTaskInstrumentation.java | 43 ---- .../MongoClientInstrumentationModule.java | 1 - .../mongo/v4_0/TaskWrapper.java | 27 --- .../test/resources/docker_proc_self_mountinfo | 2 + .../test/resources/podman_proc_self_mountinfo | 2 + .../test/groovy/ContextPropagationTest.groovy | 199 ---------------- .../rabbit/v1_0/ContextPropagationTest.java | 215 ++++++++++++++++++ settings.gradle.kts | 2 +- .../images/fake-backend/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- 33 files changed, 517 insertions(+), 607 deletions(-) delete mode 100644 instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java delete mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java delete mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java delete mode 100644 instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy create mode 100644 instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java diff --git a/benchmark-overhead-jmh/build.gradle.kts b/benchmark-overhead-jmh/build.gradle.kts index cb555af356dc..5f78b9846b55 100644 --- a/benchmark-overhead-jmh/build.gradle.kts +++ b/benchmark-overhead-jmh/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } dependencies { - jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.1") + jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.0") } tasks { diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index 17dc508103e7..aebd8e1b190c 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -59,8 +59,8 @@ dependencies { implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") - implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") - implementation("org.owasp:dependency-check-gradle:8.3.1") + implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.3") + implementation("org.owasp:dependency-check-gradle:8.2.1") implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.0") // When updating, also update dependencyManagement/build.gradle.kts implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.14.5") diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index c2eb71beb3f5..878f5750e433 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -84,10 +84,10 @@ val DEPENDENCIES = listOf( "com.github.stefanbirkner:system-lambda:1.2.1", "com.github.stefanbirkner:system-rules:1.19.0", "uk.org.webcompere:system-stubs-jupiter:2.0.2", - "com.uber.nullaway:nullaway:0.10.11", + "com.uber.nullaway:nullaway:0.10.10", "commons-beanutils:commons-beanutils:1.9.4", "commons-cli:commons-cli:1.5.0", - "commons-codec:commons-codec:1.16.0", + "commons-codec:commons-codec:1.15", "commons-collections:commons-collections:3.2.2", "commons-digester:commons-digester:2.1", "commons-fileupload:commons-fileupload:1.5", diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index abb4423d7b36..65c47d5ad219 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -70,7 +70,7 @@ subprojects { implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:${versions.opentelemetryJavaagent}")) implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${versions.opentelemetryJavaagentAlpha}")) - testImplementation("org.mockito:mockito-core:5.4.0") + testImplementation("org.mockito:mockito-core:5.3.1") testImplementation(enforcedPlatform("org.junit:junit-bom:${versions.junit}")) testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}") diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java deleted file mode 100644 index 2d233f493c21..000000000000 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.instrumenter.network; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; -import java.net.InetSocketAddress; -import javax.annotation.Nullable; -import org.junit.jupiter.api.Test; - -class ClientAttributesExtractorInetSocketAddressTest { - - static class TestClientAttributesGetter - implements ClientAttributesGetter { - - @Nullable - @Override - public String getClientAddress(InetSocketAddress request) { - // covered in ClientAttributesExtractorTest - return null; - } - - @Nullable - @Override - public Integer getClientPort(InetSocketAddress request) { - // covered in ClientAttributesExtractorTest - return null; - } - - @Nullable - @Override - public InetSocketAddress getClientInetSocketAddress( - InetSocketAddress request, @Nullable Void response) { - return request; - } - } - - @Test - void fullAddress() { - InetSocketAddress address = new InetSocketAddress("api.github.com", 456); - assertThat(address.getAddress().getHostAddress()).isNotNull(); - - AttributesExtractor extractor = - ClientAttributesExtractor.create(new TestClientAttributesGetter()); - - AttributesBuilder startAttributes = Attributes.builder(); - extractor.onStart(startAttributes, Context.root(), address); - assertThat(startAttributes.build()).isEmpty(); - - AttributesBuilder endAttributes = Attributes.builder(); - extractor.onEnd(endAttributes, Context.root(), address, null, null); - assertThat(endAttributes.build()) - .containsOnly( - entry(NetworkAttributes.CLIENT_SOCKET_ADDRESS, address.getAddress().getHostAddress()), - entry(NetworkAttributes.CLIENT_SOCKET_PORT, 456L)); - } - - @Test - void noAttributes() { - AttributesExtractor extractor = - ClientAttributesExtractor.create(new TestClientAttributesGetter()); - - AttributesBuilder startAttributes = Attributes.builder(); - extractor.onStart(startAttributes, Context.root(), null); - assertThat(startAttributes.build()).isEmpty(); - - AttributesBuilder endAttributes = Attributes.builder(); - extractor.onEnd(endAttributes, Context.root(), null, null, null); - assertThat(endAttributes.build()).isEmpty(); - } -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index c33b3b1fa314..4def26b6629d 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -15,23 +15,9 @@ muzzle { // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") } +} - fail { - group.set("software.amazon.awssdk") - module.set("aws-core") - versions.set("[2.2.0,)") - // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP - // client, which is not target of instrumentation anyways. - extraDependency("software.amazon.awssdk:protocol-core") - - // "fail" asserts that *all* the instrumentation modules fail to load, but the core one is - // actually expected to succeed, so exclude it from checks. - excludeInstrumentationName("aws-sdk-2.2-core") - - // several software.amazon.awssdk artifacts are missing for this version - skip("2.17.200") - } - +muzzle { pass { group.set("software.amazon.awssdk") module.set("sqs") @@ -67,26 +53,17 @@ dependencies { latestDepTestLibrary("software.amazon.awssdk:sqs:+") } -tasks { - val testExperimentalSqs by registering(Test::class) { - group = "verification" - - systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", "true") - } - - check { - dependsOn(testExperimentalSqs) - } - - withType().configureEach { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) - // TODO run tests both with and without experimental span attributes - systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", "true") - } +tasks.withType().configureEach { + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + // TODO run tests both with and without experimental span attributes, with & without extra propagation + systemProperties(mapOf( + "otel.instrumentation.aws-sdk.experimental-span-attributes" to "true", + "otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging" to "true", + )) +} - withType().configureEach { - mergeServiceFiles { - include("software/amazon/awssdk/global/handlers/execution.interceptors") - } +tasks.withType().configureEach { + mergeServiceFiles { + include("software/amazon/awssdk/global/handlers/execution.interceptors") } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java index 25b7b5c06c38..e3cc3579cd3c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java @@ -5,16 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.named; - import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; abstract class AbstractAwsSdkInstrumentationModule extends InstrumentationModule { @@ -26,34 +17,4 @@ protected AbstractAwsSdkInstrumentationModule(String additionalInstrumentationNa public boolean isHelperClass(String className) { return className.startsWith("io.opentelemetry.contrib.awsxray."); } - - @Override - public ElementMatcher.Junction classLoaderMatcher() { - // We don't actually transform it but want to make sure we only apply the instrumentation when - // our key dependency is present. - return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); - } - - @Override - public List typeInstrumentations() { - return singletonList(new ResourceInjectingTypeInstrumentation()); - } - - abstract void doTransform(TypeTransformer transformer); - - // A type instrumentation is needed to trigger resource injection. - public class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure - // our interceptor service definition is injected as early as possible if we typematch against - // it. - return named("software.amazon.awssdk.core.SdkClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - doTransform(transformer); - } - } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java index a6bb33a49b77..8ee1719a7c2e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java @@ -5,11 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.named; + import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor; import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class AwsSdkInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -27,7 +35,30 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) } @Override - void doTransform(TypeTransformer transformer) { - // Nothing to transform, this type instrumentation is only used for injecting resources. + public ElementMatcher.Junction classLoaderMatcher() { + // We don't actually transform it but want to make sure we only apply the instrumentation when + // our key dependency is present. + return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ResourceInjectingTypeInstrumentation()); + } + + // A type instrumentation is needed to trigger resource injection. + public static class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure + // our interceptor service definition is injected as early as possible if we typematch against + // it. + return named("software.amazon.awssdk.core.SdkClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + // Nothing to transform, this type instrumentation is only used for injecting resources. + } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java index 3fdab6da5d2f..380443ab96a1 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java @@ -5,13 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static net.bytebuddy.matcher.ElementMatchers.none; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.SqsAdviceBridge; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class SqsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -21,9 +27,21 @@ public SqsInstrumentationModule() { } @Override - public void doTransform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - none(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); + public List typeInstrumentations() { + return singletonList(new DefaultSqsClientTypeInstrumentation()); + } + + public static class DefaultSqsClientTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("software.amazon.awssdk.services.sqs.DefaultSqsClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isConstructor(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); + } } @SuppressWarnings("unused") @@ -32,7 +50,7 @@ public static class RegisterAdvice { public static void onExit() { // (indirectly) using SqsImpl class here to make sure it is available from SqsAccess // (injected into app classloader) and checked by Muzzle - SqsAdviceBridge.referenceForMuzzleOnly(); + SqsAdviceBridge.init(); } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 8ff9545dcb61..56f43c7e1668 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -26,6 +26,5 @@ tasks { test { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) - systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java index f1f8aad68dd1..6efff826aa29 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java @@ -8,18 +8,12 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; import software.amazon.awssdk.core.SdkRequest; -import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; -import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; -import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; -import software.amazon.awssdk.services.sqs.model.SendMessageRequest; // helper class for calling methods that use sqs types in SqsImpl // if SqsImpl is not present these methods are no op final class SqsAccess { - private SqsAccess() {} - private static final boolean enabled = isSqsImplPresent(); private static boolean isSqsImplPresent() { @@ -37,34 +31,14 @@ private static boolean isSqsImplPresent() { } @NoMuzzle - static boolean isSendMessageRequest(SdkRequest request) { - return enabled && request instanceof SendMessageRequest; - } - - @NoMuzzle - static SdkRequest injectIntoSendMessageRequest( + static SdkRequest injectIntoSqsSendMessageRequest( TextMapPropagator messagingPropagator, SdkRequest rawRequest, io.opentelemetry.context.Context otelContext) { - assert enabled; // enabled checked already in instance check. - return SqsImpl.injectIntoSendMessageRequest(messagingPropagator, rawRequest, otelContext); - } - - @NoMuzzle - static boolean isReceiveMessageRequest(SdkRequest request) { - return enabled && request instanceof ReceiveMessageRequest; - } - - @NoMuzzle - public static SdkRequest modifyReceiveMessageRequest( - SdkRequest request, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { - assert enabled; // enabled checked already in instance check. - return SqsImpl.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); - } - - @NoMuzzle - static boolean isReceiveMessageResponse(SdkResponse response) { - return enabled && response instanceof ReceiveMessageResponse; + if (!enabled) { + return rawRequest; + } + return SqsImpl.injectIntoSqsSendMessageRequest(messagingPropagator, rawRequest, otelContext); } @NoMuzzle @@ -72,7 +46,11 @@ static void afterReceiveMessageExecution( TracingExecutionInterceptor config, Context.AfterExecution context, ExecutionAttributes executionAttributes) { - assert enabled; // enabled checked already in instance check. - SqsImpl.afterReceiveMessageExecution(config, executionAttributes, context); + if (!enabled) { + return; + } + SqsImpl.afterConsumerResponse(config, executionAttributes, context); } + + private SqsAccess() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java index ddb3c05c5cbb..20eebbeac8e2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java @@ -8,8 +8,8 @@ public final class SqsAdviceBridge { private SqsAdviceBridge() {} - public static void referenceForMuzzleOnly() { - throw new UnsupportedOperationException( - SqsImpl.class.getName() + " referencing for muzzle, should never be actually called"); + public static void init() { + // called from advice + SqsImpl.init(); // Reference the actual, package-private, implementation class for Muzzle } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java index df3b402206f8..99c1f155450e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java @@ -7,9 +7,7 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.interceptor.Context; @@ -17,7 +15,6 @@ import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; -import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; @@ -25,7 +22,11 @@ final class SqsImpl { private SqsImpl() {} - static SdkRequest injectIntoSendMessageRequest( + public static void init() { + // called from advice + } + + static SdkRequest injectIntoSqsSendMessageRequest( TextMapPropagator messagingPropagator, SdkRequest rawRequest, io.opentelemetry.context.Context otelContext) { @@ -47,7 +48,7 @@ static SdkRequest injectIntoSendMessageRequest( } /** Create and close CONSUMER span for each message consumed. */ - static void afterReceiveMessageExecution( + static void afterConsumerResponse( TracingExecutionInterceptor config, ExecutionAttributes executionAttributes, Context.AfterExecution context) { @@ -90,44 +91,4 @@ private static void createConsumerSpan( consumerInstrumenter.end(context, executionAttributes, httpResponse, null); } } - - static SdkRequest modifyReceiveMessageRequest( - SdkRequest rawRequest, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { - ReceiveMessageRequest request = (ReceiveMessageRequest) rawRequest; - boolean hasXrayAttribute = true; - List existingAttributeNames = null; - if (useXrayPropagator) { - existingAttributeNames = request.attributeNamesAsStrings(); - hasXrayAttribute = - existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - } - - boolean hasMessageAttribute = true; - List existingMessageAttributeNames = null; - if (messagingPropagator != null) { - existingMessageAttributeNames = request.messageAttributeNames(); - hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); - } - - if (hasMessageAttribute && hasXrayAttribute) { - return request; - } - - ReceiveMessageRequest.Builder builder = request.toBuilder(); - if (!hasXrayAttribute) { - List attributeNames = new ArrayList<>(existingAttributeNames); - attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - builder.attributeNamesWithStrings(attributeNames); - } - if (messagingPropagator != null) { - List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); - for (String field : messagingPropagator.fields()) { - if (!existingMessageAttributeNames.contains(field)) { - messageAttributeNames.add(field); - } - } - builder.messageAttributeNames(messageAttributeNames); - } - return builder.build(); - } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java new file mode 100644 index 000000000000..b6ac23ded0fe --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java @@ -0,0 +1,122 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static java.lang.invoke.MethodType.methodType; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; +import software.amazon.awssdk.core.SdkRequest; + +/** + * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. + * + *

We currently don't have a good pattern of instrumenting a core library with various plugins + * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would + * prevent the entire instrumentation from loading when the plugin isn't available. We need to + * carefully check this class has all reflection errors result in no-op, and in the future we will + * hopefully come up with a better pattern. + * + * @see SDK + * Javadoc + * @see Definition + * JSON + */ +final class SqsReceiveMessageRequestAccess { + + @Nullable private static final MethodHandle ATTRIBUTE_NAMES_WITH_STRINGS; + @Nullable private static final MethodHandle MESSAGE_ATTRIBUTE_NAMES; + + static { + Class receiveMessageRequestClass = null; + try { + receiveMessageRequestClass = + Class.forName("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest$Builder"); + } catch (Throwable t) { + // Ignore. + } + if (receiveMessageRequestClass != null) { + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + MethodHandle withAttributeNames = null; + try { + withAttributeNames = + lookup.findVirtual( + receiveMessageRequestClass, + "attributeNamesWithStrings", + methodType(receiveMessageRequestClass, Collection.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + // Ignore + } + ATTRIBUTE_NAMES_WITH_STRINGS = withAttributeNames; + + MethodHandle messageAttributeNames = null; + try { + messageAttributeNames = + lookup.findVirtual( + receiveMessageRequestClass, + "messageAttributeNames", + methodType(receiveMessageRequestClass, Collection.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + // Ignore + } + MESSAGE_ATTRIBUTE_NAMES = messageAttributeNames; + } else { + ATTRIBUTE_NAMES_WITH_STRINGS = null; + MESSAGE_ATTRIBUTE_NAMES = null; + } + } + + static boolean isInstance(SdkRequest request) { + return request + .getClass() + .getName() + .equals("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest"); + } + + static void attributeNamesWithStrings(SdkRequest.Builder builder, List attributeNames) { + if (ATTRIBUTE_NAMES_WITH_STRINGS == null) { + return; + } + try { + ATTRIBUTE_NAMES_WITH_STRINGS.invoke(builder, attributeNames); + } catch (Throwable throwable) { + // Ignore + } + } + + static void messageAttributeNames( + SdkRequest.Builder builder, List messageAttributeNames) { + if (MESSAGE_ATTRIBUTE_NAMES == null) { + return; + } + try { + MESSAGE_ATTRIBUTE_NAMES.invoke(builder, messageAttributeNames); + } catch (Throwable throwable) { + // Ignore + } + } + + private SqsReceiveMessageRequestAccess() {} + + @SuppressWarnings({"rawtypes", "unchecked"}) + static List getAttributeNames(SdkRequest request) { + Optional optional = request.getValueForField("AttributeNames", List.class); + return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + static List getMessageAttributeNames(SdkRequest request) { + Optional optional = request.getValueForField("MessageAttributeNames", List.class); + return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java new file mode 100644 index 000000000000..b863ef3ceaf2 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import software.amazon.awssdk.core.SdkRequest; + +/** + * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest for points where we are not + * sure whether SQS is on the classpath. + */ +final class SqsSendMessageRequestAccess { + static boolean isInstance(SdkRequest request) { + return request + .getClass() + .getName() + .equals("software.amazon.awssdk.services.sqs.model.SendMessageRequest"); + } + + private SqsSendMessageRequestAccess() {} +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index ccdc223ec5fc..95d81cd3034d 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -15,6 +15,7 @@ import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import software.amazon.awssdk.awscore.AwsResponse; @@ -121,17 +122,56 @@ public SdkRequest modifyRequest( throw throwable; } - if (SqsAccess.isReceiveMessageRequest(request)) { - return SqsAccess.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); + if (SqsReceiveMessageRequestAccess.isInstance(request)) { + return modifySqsReceiveMessageRequest(request); } else if (messagingPropagator != null) { - if (SqsAccess.isSendMessageRequest(request)) { - return SqsAccess.injectIntoSendMessageRequest(messagingPropagator, request, otelContext); + if (SqsSendMessageRequestAccess.isInstance(request)) { + return SqsAccess.injectIntoSqsSendMessageRequest(messagingPropagator, request, otelContext); } // TODO: Support SendMessageBatchRequest (and thus SendMessageBatchRequestEntry) } return request; } + private SdkRequest modifySqsReceiveMessageRequest(SdkRequest request) { + boolean hasXrayAttribute = true; + List existingAttributeNames = null; + if (useXrayPropagator) { + existingAttributeNames = SqsReceiveMessageRequestAccess.getAttributeNames(request); + hasXrayAttribute = + existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + + boolean hasMessageAttribute = true; + List existingMessageAttributeNames = null; + if (messagingPropagator != null) { + existingMessageAttributeNames = + SqsReceiveMessageRequestAccess.getMessageAttributeNames(request); + hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); + } + + if (hasMessageAttribute && hasXrayAttribute) { + return request; + } + + SdkRequest.Builder builder = request.toBuilder(); + if (!hasXrayAttribute) { + List attributeNames = new ArrayList<>(existingAttributeNames); + attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + SqsReceiveMessageRequestAccess.attributeNamesWithStrings(builder, attributeNames); + } + if (messagingPropagator != null) { + List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); + for (String field : messagingPropagator.fields()) { + if (!existingMessageAttributeNames.contains(field)) { + messageAttributeNames.add(field); + } + } + SqsReceiveMessageRequestAccess.messageAttributeNames(builder, messageAttributeNames); + } + return builder.build(); + } + @Override public void afterMarshalling( Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { @@ -225,7 +265,7 @@ private void populateRequestAttributes( @Override public void afterExecution( Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (SqsAccess.isReceiveMessageResponse(context.response())) { + if (SqsReceiveMessageRequestAccess.isInstance(context.request())) { SqsAccess.afterReceiveMessageExecution(this, context, executionAttributes); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy index 40a88e4c5863..211773859068 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy @@ -15,7 +15,7 @@ class Aws2ClientTest extends AbstractAws2ClientTest implements LibraryTestTrait .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) + .setUseConfiguredPropagatorForMessaging(true) // Default on in tests to cover more code .build() .newExecutionInterceptor()) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index 3a10459e6dea..a3fcdca522da 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -5,14 +5,12 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil import io.opentelemetry.instrumentation.test.InstrumentationSpecification import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import io.opentelemetry.testing.internal.armeria.common.HttpResponse import io.opentelemetry.testing.internal.armeria.common.HttpStatus import io.opentelemetry.testing.internal.armeria.common.MediaType import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension -import org.junit.jupiter.api.Assumptions import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.core.ResponseInputStream @@ -50,7 +48,6 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest -import software.amazon.awssdk.services.sqs.model.SendMessageRequest import spock.lang.Shared import spock.lang.Unroll @@ -320,22 +317,7 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { return AttributeValue.builder().s(value).build() } - def isSqsAttributeInjectionEnabled() { - // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor - return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) - } - - void assumeSupportedConfig(service, operation) { - Assumptions.assumeFalse( - service == "Sqs" - && operation == "SendMessage" - && isSqsAttributeInjectionEnabled(), - "Cannot check Sqs.SendMessage here due to hard-coded MD5.") - } - def "send #operation request with builder #builder.class.getName() mocked response"() { - assumeSupportedConfig(service, operation) - setup: configureSdkClient(builder) def client = builder @@ -402,16 +384,6 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ - - - d41d8cd98f00b204e9800998ecf8427e - 3ae8f24a165a8cedc005670c81a27295 - 5fea7756-0ea4-451a-a703-a558b933e274 - - 27daac76-34dd-47df-bd01-1f6e873584a0 - - """ "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE @@ -427,7 +399,6 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { } def "send #operation async request with builder #builder.class.getName() mocked response"() { - assumeSupportedConfig(service, operation) setup: configureSdkClient(builder) def client = builder @@ -494,16 +465,6 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ - - - d41d8cd98f00b204e9800998ecf8427e - 3ae8f24a165a8cedc005670c81a27295 - 5fea7756-0ea4-451a-a703-a558b933e274 - - 27daac76-34dd-47df-bd01-1f6e873584a0 - - """ "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index a86c9bc8a81a..7a7a96efefb0 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.instrumentation.test.utils.PortUtils import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import org.elasticmq.rest.sqs.SQSRestServerBuilder import software.amazon.awssdk.auth.credentials.AwsBasicCredentials @@ -60,10 +61,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() def setupSpec() { - sqs = SQSRestServerBuilder.withPort(0).withInterface("localhost").start() - def server = sqs.waitUntilStarted() - sqsPort = server.localAddress().port + sqsPort = PortUtils.findOpenPort() + sqs = SQSRestServerBuilder.withPort(sqsPort).withInterface("localhost").start() println getClass().name + " SQS server started at: localhost:$sqsPort/" + } def cleanupSpec() { @@ -180,10 +181,9 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest) - def resp = client.receiveMessage(receiveMessageRequest) + client.receiveMessage(receiveMessageRequest) then: - resp.messages().size() == 1 assertSqsTraces() } @@ -198,10 +198,9 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest).get() - def resp = client.receiveMessage(receiveMessageRequest).get() + client.receiveMessage(receiveMessageRequest).get() then: - resp.messages().size() == 1 assertSqsTraces() } } 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 6a31624a42bf..5d0cf18b17e1 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 @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor; import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; -import java.time.Instant; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -67,7 +66,6 @@ public static void capture( .logRecordBuilder(); Map contextData = ThreadContext.getImmutableContext(); mapper.mapLogEvent(builder, message, level, marker, throwable, contextData); - builder.setTimestamp(Instant.now()); builder.emit(); } diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java index 612d9fce4cf5..4c753b52c4ee 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java @@ -8,7 +8,6 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.logs.Severity; @@ -17,7 +16,6 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.time.Instant; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -70,8 +68,6 @@ private static void test( String expectedSeverityText) throws InterruptedException { - Instant start = Instant.now(); - // when if (withParent) { testing.runWithSpan( @@ -92,11 +88,6 @@ private static void test( .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build()) .hasSeverity(expectedSeverity) .hasSeverityText(expectedSeverityText); - - assertThat(log.getTimestampEpochNanos()) - .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) - .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); - if (logException) { assertThat(log) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java index 535937e5fc3f..f35564694fcb 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java @@ -9,7 +9,6 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; @@ -25,6 +24,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.time.Instant; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -107,8 +107,8 @@ void logWithExtras() { satisfies(SemanticAttributes.EXCEPTION_STACKTRACE, v -> v.contains("logWithExtras"))); assertThat(logDataList.get(0).getTimestampEpochNanos()) - .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) - .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + .isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli())); } @Test diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java index 24994d4d1972..0eff79c30dd2 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java @@ -34,14 +34,6 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) .and(takesArgument(1, named("com.mongodb.internal.async.SingleResultCallback"))), this.getClass().getName() + "$SingleResultCallbackArg1Advice"); - - transformer.applyAdviceToMethod( - isMethod() - .and(isPublic()) - .and(named("selectServerAsync")) - .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) - .and(takesArgument(2, named("com.mongodb.internal.async.SingleResultCallback"))), - this.getClass().getName() + "$SingleResultCallbackArg2Advice"); } @SuppressWarnings("unused") @@ -53,14 +45,4 @@ public static void wrapCallback( callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); } } - - @SuppressWarnings("unused") - public static class SingleResultCallbackArg2Advice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void wrapCallback( - @Advice.Argument(value = 2, readOnly = false) SingleResultCallback callback) { - callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); - } - } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java deleted file mode 100644 index a87070f926eb..000000000000 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; - -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; - -import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.function.Consumer; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class DefaultConnectionPoolTaskInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - return named("com.mongodb.internal.connection.DefaultConnectionPool$Task"); - } - - @Override - public void transform(TypeTransformer transformer) { - // outer class this is passed as arg 0 to constructor - transformer.applyAdviceToMethod( - isConstructor().and(takesArgument(2, Consumer.class)), - this.getClass().getName() + "$TaskAdvice"); - } - - @SuppressWarnings("unused") - public static class TaskAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void wrapCallback( - @Advice.Argument(value = 2, readOnly = false) Consumer action) { - action = new TaskWrapper(Java8BytecodeBridge.currentContext(), action); - } - } -} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java index 25b8abe5c0c2..ecc578f7bced 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java @@ -33,7 +33,6 @@ public List typeInstrumentations() { new InternalStreamConnectionInstrumentation(), new BaseClusterInstrumentation(), new DefaultConnectionPoolInstrumentation(), - new DefaultConnectionPoolTaskInstrumentation(), new AsyncWorkManagerInstrumentation()); } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java deleted file mode 100644 index ea4f72229143..000000000000 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import java.util.function.Consumer; - -public class TaskWrapper implements Consumer { - private final Context context; - private final Consumer delegate; - - public TaskWrapper(Context context, Consumer delegate) { - this.context = context; - this.delegate = delegate; - } - - @Override - public void accept(Object value) { - try (Scope ignored = context.makeCurrent()) { - delegate.accept(value); - } - } -} diff --git a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo index 28af893acf71..d707c05184d9 100644 --- a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo @@ -1,3 +1,5 @@ +473 456 254:1 /docker/containers/be522444b60caf2d3934b8b24b916a8a314f4b68d4595aa419874657e8d103f2/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw +root@be522444b60c:/# cat /proc/self/mountinfo 456 375 0:143 / / rw,relatime master:175 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CBPR2ETR4Z3UMOOGIIRDVT2P27:/var/lib/docker/overlay2/l/46FCA2JFPCSNFGAR5TSYLLNHLK,upperdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/diff,workdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/work 457 456 0:146 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 466 456 0:147 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 diff --git a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo index 6fb20214fc5d..5d67bcdd1a60 100644 --- a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo @@ -1,3 +1,5 @@ +983 961 0:56 /containers/overlay-containers/2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6/userdata/hostname /etc/hostname ro,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=783888k,nr_inodes=195972,mode=700,uid=2024,gid=2024,inode64 +[root@2a33efc76e51 /]# cat /proc/self/mountinfo 961 812 0:58 / / ro,relatime - overlay overlay rw,lowerdir=/home/dracula/.local/share/containers/storage/overlay/l/4NB35A5Z4YGWDHXYEUZU4FN6BU,upperdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/diff,workdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/work,userxattr 962 961 0:63 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs rw 963 961 0:64 / /run rw,nosuid,nodev,relatime - tmpfs tmpfs rw,uid=2024,gid=2024,inode64 diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy deleted file mode 100644 index 1cd7eaf4a046..000000000000 --- a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.rabbitmq.client.ConnectionFactory -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.testing.GlobalTraceUtil -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.springframework.amqp.AmqpException -import org.springframework.amqp.core.AmqpTemplate -import org.springframework.amqp.core.Message -import org.springframework.amqp.core.MessagePostProcessor -import org.springframework.amqp.core.Queue -import org.springframework.amqp.rabbit.annotation.RabbitListener -import org.springframework.boot.SpringApplication -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.Bean -import org.testcontainers.containers.GenericContainer -import org.testcontainers.containers.wait.strategy.Wait -import spock.lang.Shared -import spock.lang.Unroll - -import java.time.Duration - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.CONSUMER -import static io.opentelemetry.api.trace.SpanKind.PRODUCER - -class ContextPropagationTest extends AgentInstrumentationSpecification { - - @Shared - GenericContainer rabbitMqContainer - @Shared - ConfigurableApplicationContext applicationContext - @Shared - ConnectionFactory connectionFactory - - def setupSpec() { - rabbitMqContainer = new GenericContainer('rabbitmq:latest') - .withExposedPorts(5672) - .waitingFor(Wait.forLogMessage(".*Server startup complete.*", 1)) - .withStartupTimeout(Duration.ofMinutes(2)) - rabbitMqContainer.start() - - def app = new SpringApplication(ConsumerConfig) - app.setDefaultProperties([ - "spring.jmx.enabled" : false, - "spring.main.web-application-type": "none", - "spring.rabbitmq.host" : rabbitMqContainer.host, - "spring.rabbitmq.port" : rabbitMqContainer.getMappedPort(5672), - ]) - applicationContext = app.run() - - connectionFactory = new ConnectionFactory( - host: rabbitMqContainer.host, - port: rabbitMqContainer.getMappedPort(5672) - ) - } - - def cleanupSpec() { - rabbitMqContainer?.stop() - applicationContext?.close() - } - - @Unroll - def "should propagate context to consumer, test headers: #testHeaders"() { - given: - def connection = connectionFactory.newConnection() - def channel = connection.createChannel() - - when: - runWithSpan("parent") { - if (testHeaders) { - applicationContext.getBean(AmqpTemplate) - .convertAndSend(ConsumerConfig.TEST_QUEUE, (Object) "test", new MessagePostProcessor() { - @Override - Message postProcessMessage(Message message) throws AmqpException { - message.getMessageProperties().setHeader("test-message-header", "test") - return message - } - }) - } else { - applicationContext.getBean(AmqpTemplate) - .convertAndSend(ConsumerConfig.TEST_QUEUE, "test") - } - } - - then: - assertTraces(2) { - trace(0, 5) { - spans.subList(2, 5).sort { - // sort "consumer" span after "testQueue process" spans - if (it.name == "consumer") { - return 2 - } - // order "testQueue process" spans - def destination = it.attributes.get(SemanticAttributes.MESSAGING_DESTINATION_NAME) - return destination == "" ? 0 : 1 - } - span(0) { - name "parent" - } - span(1) { - // created by rabbitmq instrumentation - name " send" - kind PRODUCER - childOf span(0) - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" { it == "127.0.0.1" || it == null } - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" "" - "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long - "$SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY" String - if (testHeaders) { - "messaging.header.test_message_header" { it == ["test"] } - } - } - } - // spring-cloud-stream-binder-rabbit listener puts all messages into a BlockingQueue immediately after receiving - // that's why the rabbitmq CONSUMER span will never have any child span (and propagate context, actually) - span(2) { - // created by rabbitmq instrumentation - name "testQueue process" - kind CONSUMER - childOf span(1) - attributes { - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" "" - "$SemanticAttributes.MESSAGING_OPERATION" "process" - "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long - "$SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY" String - if (testHeaders) { - "messaging.header.test_message_header" { it == ["test"] } - } - } - } - span(3) { - // created by spring-rabbit instrumentation - name "testQueue process" - kind CONSUMER - childOf span(1) - attributes { - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" "testQueue" - "$SemanticAttributes.MESSAGING_OPERATION" "process" - "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long - if (testHeaders) { - "messaging.header.test_message_header" { it == ["test"] } - } - } - } - span(4) { - name "consumer" - childOf span(3) - } - } - trace(1, 1) { - span(0) { - // created by rabbitmq instrumentation - name "basic.ack" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" { it == "127.0.0.1" || it == null } - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - } - } - } - } - - cleanup: - channel?.close() - connection?.close() - - where: - testHeaders << [false, true] - } - - @SpringBootConfiguration - @EnableAutoConfiguration - static class ConsumerConfig { - - static final String TEST_QUEUE = "testQueue" - - @Bean - Queue testQueue() { - new Queue(TEST_QUEUE) - } - - @RabbitListener(queues = TEST_QUEUE) - void consume(String ignored) { - GlobalTraceUtil.runWithSpan("consumer") {} - } - } -} diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java new file mode 100644 index 000000000000..d01343ea71de --- /dev/null +++ b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java @@ -0,0 +1,215 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.rabbit.v1_0; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.GlobalTraceUtil; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.AbstractLongAssert; +import org.assertj.core.api.AbstractStringAssert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +public class ContextPropagationTest { + + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static GenericContainer rabbitMqContainer; + private static ConfigurableApplicationContext applicationContext; + private static ConnectionFactory connectionFactory; + + @BeforeAll + static void setUp() { + rabbitMqContainer = + new GenericContainer<>("rabbitmq:latest") + .withExposedPorts(5672) + .waitingFor(Wait.forLogMessage(".*Server startup complete.*", 1)) + .withStartupTimeout(Duration.ofMinutes(2)); + rabbitMqContainer.start(); + + SpringApplication app = new SpringApplication(ConsumerConfig.class); + Map props = new HashMap<>(); + props.put("spring.jmx.enabled", false); + props.put("spring.main.web-application-type", "none"); + props.put("spring.rabbitmq.host", rabbitMqContainer.getHost()); + props.put("spring.rabbitmq.port", rabbitMqContainer.getMappedPort(5672)); + app.setDefaultProperties(props); + + applicationContext = app.run(); + + connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(rabbitMqContainer.getHost()); + connectionFactory.setPort(rabbitMqContainer.getMappedPort(5672)); + } + + @AfterAll + static void teardown() { + if (rabbitMqContainer != null) { + rabbitMqContainer.stop(); + } + if (applicationContext != null) { + applicationContext.close(); + } + } + + private static List getAssertions( + String destination, + String operation, + String sockAddr, + boolean routingKey, + boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "rabbitmq"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destination), + satisfies( + SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, + AbstractLongAssert::isNotNegative))); + if (operation != null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_OPERATION, operation)); + } + if (sockAddr != null) { + assertions.add(equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, sockAddr)); + assertions.add( + satisfies(SemanticAttributes.NET_SOCK_PEER_PORT, AbstractLongAssert::isNotNegative)); + } + if (routingKey) { + assertions.add( + satisfies( + SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, + AbstractStringAssert::isNotBlank)); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void test(boolean testHeaders) throws Exception { + try (Connection connection = connectionFactory.newConnection()) { + try (Channel ignored = connection.createChannel()) { + testing.runWithSpan( + "parent", + () -> { + if (testHeaders) { + applicationContext + .getBean(AmqpTemplate.class) + .convertAndSend( + ConsumerConfig.TEST_QUEUE, + "test", + message -> { + message.getMessageProperties().setHeader("test-message-header", "test"); + return message; + }); + } else { + applicationContext + .getBean(AmqpTemplate.class) + .convertAndSend(ConsumerConfig.TEST_QUEUE, "test"); + } + }); + testing.waitAndAssertTraces( + trace -> { + trace + .hasSize(5) + .hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> + span.hasName(" send") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + getAssertions("", null, "127.0.0.1", true, testHeaders)), + // spring-cloud-stream-binder-rabbit listener puts all messages into a + // BlockingQueue immediately after receiving + // that's why the rabbitmq CONSUMER span will never have any child span (and + // propagate context, actually) + span -> + span.hasName("testQueue process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + getAssertions("", "process", null, true, testHeaders)), + // created by spring-rabbit instrumentation + span -> + span.hasName("testQueue process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + getAssertions("testQueue", "process", null, false, testHeaders)), + span -> span.hasName("consumer").hasParent(trace.getSpan(3))); + }, + trace -> { + trace + .hasSize(1) + .hasSpansSatisfyingExactly( + span -> + span.hasName("basic.ack") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + satisfies( + SemanticAttributes.NET_SOCK_PEER_PORT, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "rabbitmq"))); + }); + } + } + } + + @SpringBootConfiguration + @EnableAutoConfiguration + static class ConsumerConfig { + + static final String TEST_QUEUE = "testQueue"; + + @Bean + Queue testQueue() { + return new Queue(TEST_QUEUE); + } + + @RabbitListener(queues = TEST_QUEUE) + void consume(String ignored) { + GlobalTraceUtil.runWithSpan("consumer", () -> {}); + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e376d02ed66e..f93c66b24642 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,7 @@ pluginManagement { } plugins { - id("com.gradle.enterprise") version "3.13.4" + id("com.gradle.enterprise") version "3.13.3" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11" id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } diff --git a/smoke-tests/images/fake-backend/build.gradle.kts b/smoke-tests/images/fake-backend/build.gradle.kts index 0cb6d572d99a..5baad1474ef0 100644 --- a/smoke-tests/images/fake-backend/build.gradle.kts +++ b/smoke-tests/images/fake-backend/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-grpc:1.24.1") + implementation("com.linecorp.armeria:armeria-grpc:1.24.0") implementation("io.opentelemetry.proto:opentelemetry-proto") runtimeOnly("org.slf4j:slf4j-simple") } diff --git a/testing/armeria-shaded-for-testing/build.gradle.kts b/testing/armeria-shaded-for-testing/build.gradle.kts index f34e1717a668..481fd608e578 100644 --- a/testing/armeria-shaded-for-testing/build.gradle.kts +++ b/testing/armeria-shaded-for-testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-junit5:1.24.1") + implementation("com.linecorp.armeria:armeria-junit5:1.24.0") } tasks { From fac22533f1cfbdf64130a9139ebc4f9d0e5b8f00 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 28 Jun 2023 11:56:38 +0300 Subject: [PATCH 18/95] Fix context leak on call to AmazonS3.generatePresignedUrl (#8815) --- .../aws-sdk-1.11/javaagent/build.gradle.kts | 3 +++ .../awssdk/v1_11/Aws1ClientTest.groovy | 19 +++++++++++++++++++ .../groovy/Aws0ClientTest.groovy | 16 ++++++++++++++++ .../awssdk/v1_11/TracingRequestHandler.java | 8 ++++++++ 4 files changed, 46 insertions(+) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts index c226dad0fc1e..b654433cefe6 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts @@ -65,6 +65,9 @@ testing { implementation("com.amazonaws:aws-java-sdk-kinesis:1.11.0") implementation("com.amazonaws:aws-java-sdk-dynamodb:1.11.0") implementation("com.amazonaws:aws-java-sdk-sns:1.11.0") + + // needed by S3 + implementation("javax.xml.bind:jaxb-api:2.3.1") } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy index c4a4593ef0c1..b8dc9f841c72 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy @@ -6,8 +6,11 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11 import com.amazonaws.AmazonWebServiceClient +import com.amazonaws.ClientConfiguration import com.amazonaws.Request import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.auth.NoOpSigner +import com.amazonaws.auth.SignerFactory import com.amazonaws.handlers.RequestHandler2 import com.amazonaws.regions.Regions import com.amazonaws.services.s3.AmazonS3Client @@ -110,4 +113,20 @@ class Aws1ClientTest extends AbstractAws1ClientTest implements AgentTestTrait { } } } + + def "calling generatePresignedUrl does not leak context"() { + setup: + SignerFactory.registerSigner("noop", NoOpSigner) + def client = AmazonS3ClientBuilder.standard() + .withRegion(Regions.US_EAST_1) + .withClientConfiguration(new ClientConfiguration().withSignerOverride("noop")) + .build() + + when: + client.generatePresignedUrl("someBucket", "someKey", new Date()) + + then: + // expecting no active span after call to generatePresignedUrl + !Span.current().getSpanContext().isValid() + } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy index f21ab34c2073..ceb6e4258bae 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy @@ -11,6 +11,8 @@ import com.amazonaws.auth.AWSCredentialsProviderChain import com.amazonaws.auth.BasicAWSCredentials import com.amazonaws.auth.EnvironmentVariableCredentialsProvider import com.amazonaws.auth.InstanceProfileCredentialsProvider +import com.amazonaws.auth.NoOpSigner +import com.amazonaws.auth.SignerFactory import com.amazonaws.auth.SystemPropertiesCredentialsProvider import com.amazonaws.auth.profile.ProfileCredentialsProvider import com.amazonaws.handlers.RequestHandler2 @@ -272,4 +274,18 @@ class Aws0ClientTest extends AgentInstrumentationSpecification { } } } + + def "calling generatePresignedUrl does not leak context"() { + setup: + SignerFactory.registerSigner("noop", NoOpSigner) + def client = new AmazonS3Client(new ClientConfiguration().withSignerOverride("noop")) + .withEndpoint("${server.httpUri()}") + + when: + client.generatePresignedUrl("someBucket", "someKey", new Date()) + + then: + // expecting no active span after call to generatePresignedUrl + !Span.current().getSpanContext().isValid() + } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java index c427c9f729c5..a6755552197c 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java @@ -36,6 +36,14 @@ final class TracingRequestHandler extends RequestHandler2 { @Override @SuppressWarnings("deprecation") // deprecated class to be updated once published in new location public void beforeRequest(Request request) { + // GeneratePresignedUrlRequest doesn't result in actual request, beforeRequest is the only + // method called for it. Span created here would never be ended and scope would be leaked when + // running with java agent. + if ("com.amazonaws.services.s3.model.GeneratePresignedUrlRequest" + .equals(request.getOriginalRequest().getClass().getName())) { + return; + } + Context parentContext = Context.current(); if (!requestInstrumenter.shouldStart(parentContext, request)) { return; From 47159e6a7ce206ab1a738fe67ab06671dc93377a Mon Sep 17 00:00:00 2001 From: Anthony Quinones Date: Wed, 28 Jun 2023 04:15:30 -0500 Subject: [PATCH 19/95] convert dropwizard-views-0.7 tests from groovy to java (#8808) --- .../src/test/groovy/ViewRenderTest.groovy | 63 ----------------- .../dropwizardviews/ViewRenderTest.java | 69 +++++++++++++++++++ 2 files changed, 69 insertions(+), 63 deletions(-) delete mode 100644 instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy create mode 100644 instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java diff --git a/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy b/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy deleted file mode 100644 index 414926d5658f..000000000000 --- a/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.dropwizard.views.View -import io.dropwizard.views.freemarker.FreemarkerViewRenderer -import io.dropwizard.views.mustache.MustacheViewRenderer -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification - -import java.nio.charset.StandardCharsets - -class ViewRenderTest extends AgentInstrumentationSpecification { - - def "render #template succeeds with span"() { - setup: - def outputStream = new ByteArrayOutputStream() - - when: - runWithSpan("parent") { - renderer.render(view, Locale.ENGLISH, outputStream) - } - - then: - outputStream.toString().contains("This is an example of a view") - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - span(1) { - name "Render $template" - childOf span(0) - } - } - } - - where: - renderer | template - new FreemarkerViewRenderer() | "/views/ftl/utf8.ftl" - new MustacheViewRenderer() | "/views/mustache/utf8.mustache" - new FreemarkerViewRenderer() | "/views/ftl/utf8.ftl" - new MustacheViewRenderer() | "/views/mustache/utf8.mustache" - - view = new View(template, StandardCharsets.UTF_8) {} - } - - def "do not create span when there's no parent"() { - setup: - def outputStream = new ByteArrayOutputStream() - def view = new View("/views/ftl/utf8.ftl", StandardCharsets.UTF_8) {} - - when: - new FreemarkerViewRenderer().render(view, Locale.ENGLISH, outputStream) - - then: - Thread.sleep(500) - assert traces.isEmpty() - } -} diff --git a/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java b/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java new file mode 100644 index 000000000000..1020b616cb46 --- /dev/null +++ b/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.dropwizardviews; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.dropwizard.views.View; +import io.dropwizard.views.ViewRenderer; +import io.dropwizard.views.freemarker.FreemarkerViewRenderer; +import io.dropwizard.views.mustache.MustacheViewRenderer; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ViewRenderTest { + + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static Stream provideParameters() { + return Stream.of( + Arguments.of(new FreemarkerViewRenderer(), "/views/ftl/utf8.ftl"), + Arguments.of(new MustacheViewRenderer(), "/views/mustache/utf8.mustache"), + Arguments.of(new FreemarkerViewRenderer(), "/views/ftl/utf8.ftl"), + Arguments.of(new MustacheViewRenderer(), "/views/mustache/utf8.mustache")); + } + + @ParameterizedTest + @MethodSource("provideParameters") + void testSpan(ViewRenderer renderer, String template) throws IOException { + View view = new View(template, StandardCharsets.UTF_8) {}; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + testing.runWithSpan( + "parent", + () -> { + renderer.render(view, Locale.ENGLISH, outputStream); + }); + assertThat(outputStream.toString("UTF-8")).contains("This is an example of a view"); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> span.hasName("Render " + template).hasParent(trace.getSpan(0)))); + } + + @Test + void testDoesNotCreateSpanWithoutParent() throws InterruptedException, IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + View view = new View("/views/ftl/utf8.ftl", StandardCharsets.UTF_8) {}; + new FreemarkerViewRenderer().render(view, Locale.ENGLISH, outputStream); + Thread.sleep(500); + assertThat(testing.spans().size()).isEqualTo(0); + } +} From 9e37e724dcb8b2bf1d56b3ab88f8d8084f98045d Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 28 Jun 2023 12:55:41 +0200 Subject: [PATCH 20/95] Fix the main branch history (#8817) --- benchmark-overhead-jmh/build.gradle.kts | 2 +- conventions/build.gradle.kts | 4 +- dependencyManagement/build.gradle.kts | 4 +- examples/distro/build.gradle | 2 +- ...ributesExtractorInetSocketAddressTest.java | 80 ++++++++++++ .../aws-sdk-2.2/javaagent/build.gradle.kts | 49 +++++-- .../AbstractAwsSdkInstrumentationModule.java | 39 ++++++ .../v2_2/AwsSdkInstrumentationModule.java | 35 +---- .../awssdk/v2_2/SqsInstrumentationModule.java | 28 +--- .../aws-sdk-2.2/library/build.gradle.kts | 1 + .../awssdk/v2_2/SqsAccess.java | 44 +++++-- .../awssdk/v2_2/SqsAdviceBridge.java | 6 +- .../instrumentation/awssdk/v2_2/SqsImpl.java | 51 +++++++- .../v2_2/SqsReceiveMessageRequestAccess.java | 122 ------------------ .../v2_2/SqsSendMessageRequestAccess.java | 23 ---- .../v2_2/TracingExecutionInterceptor.java | 50 +------ .../awssdk/v2_2/Aws2ClientTest.groovy | 2 +- .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 39 ++++++ .../v2_2/AbstractAws2SqsTracingTest.groovy | 13 +- .../log4j/appender/v2_17/Log4jHelper.java | 2 + .../log4j/appender/v2_17/Log4j2Test.java | 9 ++ .../OpenTelemetryAppenderConfigTestBase.java | 6 +- .../v4_0/BaseClusterInstrumentation.java | 18 +++ ...aultConnectionPoolTaskInstrumentation.java | 43 ++++++ .../MongoClientInstrumentationModule.java | 1 + .../mongo/v4_0/TaskWrapper.java | 27 ++++ .../test/resources/docker_proc_self_mountinfo | 2 - .../test/resources/podman_proc_self_mountinfo | 2 - settings.gradle.kts | 2 +- .../images/fake-backend/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- 31 files changed, 408 insertions(+), 302 deletions(-) create mode 100644 instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java delete mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java delete mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java create mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java create mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java diff --git a/benchmark-overhead-jmh/build.gradle.kts b/benchmark-overhead-jmh/build.gradle.kts index 5f78b9846b55..cb555af356dc 100644 --- a/benchmark-overhead-jmh/build.gradle.kts +++ b/benchmark-overhead-jmh/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } dependencies { - jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.0") + jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.1") } tasks { diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index aebd8e1b190c..17dc508103e7 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -59,8 +59,8 @@ dependencies { implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") - implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.3") - implementation("org.owasp:dependency-check-gradle:8.2.1") + implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") + implementation("org.owasp:dependency-check-gradle:8.3.1") implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.0") // When updating, also update dependencyManagement/build.gradle.kts implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.14.5") diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 878f5750e433..c2eb71beb3f5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -84,10 +84,10 @@ val DEPENDENCIES = listOf( "com.github.stefanbirkner:system-lambda:1.2.1", "com.github.stefanbirkner:system-rules:1.19.0", "uk.org.webcompere:system-stubs-jupiter:2.0.2", - "com.uber.nullaway:nullaway:0.10.10", + "com.uber.nullaway:nullaway:0.10.11", "commons-beanutils:commons-beanutils:1.9.4", "commons-cli:commons-cli:1.5.0", - "commons-codec:commons-codec:1.15", + "commons-codec:commons-codec:1.16.0", "commons-collections:commons-collections:3.2.2", "commons-digester:commons-digester:2.1", "commons-fileupload:commons-fileupload:1.5", diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index 65c47d5ad219..abb4423d7b36 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -70,7 +70,7 @@ subprojects { implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:${versions.opentelemetryJavaagent}")) implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${versions.opentelemetryJavaagentAlpha}")) - testImplementation("org.mockito:mockito-core:5.3.1") + testImplementation("org.mockito:mockito-core:5.4.0") testImplementation(enforcedPlatform("org.junit:junit-bom:${versions.junit}")) testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}") diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java new file mode 100644 index 000000000000..2d233f493c21 --- /dev/null +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import java.net.InetSocketAddress; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; + +class ClientAttributesExtractorInetSocketAddressTest { + + static class TestClientAttributesGetter + implements ClientAttributesGetter { + + @Nullable + @Override + public String getClientAddress(InetSocketAddress request) { + // covered in ClientAttributesExtractorTest + return null; + } + + @Nullable + @Override + public Integer getClientPort(InetSocketAddress request) { + // covered in ClientAttributesExtractorTest + return null; + } + + @Nullable + @Override + public InetSocketAddress getClientInetSocketAddress( + InetSocketAddress request, @Nullable Void response) { + return request; + } + } + + @Test + void fullAddress() { + InetSocketAddress address = new InetSocketAddress("api.github.com", 456); + assertThat(address.getAddress().getHostAddress()).isNotNull(); + + AttributesExtractor extractor = + ClientAttributesExtractor.create(new TestClientAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), address); + assertThat(startAttributes.build()).isEmpty(); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), address, null, null); + assertThat(endAttributes.build()) + .containsOnly( + entry(NetworkAttributes.CLIENT_SOCKET_ADDRESS, address.getAddress().getHostAddress()), + entry(NetworkAttributes.CLIENT_SOCKET_PORT, 456L)); + } + + @Test + void noAttributes() { + AttributesExtractor extractor = + ClientAttributesExtractor.create(new TestClientAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), null); + assertThat(startAttributes.build()).isEmpty(); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), null, null, null); + assertThat(endAttributes.build()).isEmpty(); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index 4def26b6629d..c33b3b1fa314 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -15,9 +15,23 @@ muzzle { // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") } -} -muzzle { + fail { + group.set("software.amazon.awssdk") + module.set("aws-core") + versions.set("[2.2.0,)") + // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP + // client, which is not target of instrumentation anyways. + extraDependency("software.amazon.awssdk:protocol-core") + + // "fail" asserts that *all* the instrumentation modules fail to load, but the core one is + // actually expected to succeed, so exclude it from checks. + excludeInstrumentationName("aws-sdk-2.2-core") + + // several software.amazon.awssdk artifacts are missing for this version + skip("2.17.200") + } + pass { group.set("software.amazon.awssdk") module.set("sqs") @@ -53,17 +67,26 @@ dependencies { latestDepTestLibrary("software.amazon.awssdk:sqs:+") } -tasks.withType().configureEach { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) - // TODO run tests both with and without experimental span attributes, with & without extra propagation - systemProperties(mapOf( - "otel.instrumentation.aws-sdk.experimental-span-attributes" to "true", - "otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging" to "true", - )) -} +tasks { + val testExperimentalSqs by registering(Test::class) { + group = "verification" + + systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", "true") + } + + check { + dependsOn(testExperimentalSqs) + } + + withType().configureEach { + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + // TODO run tests both with and without experimental span attributes + systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", "true") + } -tasks.withType().configureEach { - mergeServiceFiles { - include("software/amazon/awssdk/global/handlers/execution.interceptors") + withType().configureEach { + mergeServiceFiles { + include("software/amazon/awssdk/global/handlers/execution.interceptors") + } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java index e3cc3579cd3c..25b7b5c06c38 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java @@ -5,7 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.named; + import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; abstract class AbstractAwsSdkInstrumentationModule extends InstrumentationModule { @@ -17,4 +26,34 @@ protected AbstractAwsSdkInstrumentationModule(String additionalInstrumentationNa public boolean isHelperClass(String className) { return className.startsWith("io.opentelemetry.contrib.awsxray."); } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // We don't actually transform it but want to make sure we only apply the instrumentation when + // our key dependency is present. + return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ResourceInjectingTypeInstrumentation()); + } + + abstract void doTransform(TypeTransformer transformer); + + // A type instrumentation is needed to trigger resource injection. + public class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure + // our interceptor service definition is injected as early as possible if we typematch against + // it. + return named("software.amazon.awssdk.core.SdkClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + doTransform(transformer); + } + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java index 8ee1719a7c2e..a6bb33a49b77 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java @@ -5,19 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.named; - import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor; import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class AwsSdkInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -35,30 +27,7 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) } @Override - public ElementMatcher.Junction classLoaderMatcher() { - // We don't actually transform it but want to make sure we only apply the instrumentation when - // our key dependency is present. - return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); - } - - @Override - public List typeInstrumentations() { - return singletonList(new ResourceInjectingTypeInstrumentation()); - } - - // A type instrumentation is needed to trigger resource injection. - public static class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure - // our interceptor service definition is injected as early as possible if we typematch against - // it. - return named("software.amazon.awssdk.core.SdkClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - // Nothing to transform, this type instrumentation is only used for injecting resources. - } + void doTransform(TypeTransformer transformer) { + // Nothing to transform, this type instrumentation is only used for injecting resources. } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java index 380443ab96a1..3fdab6da5d2f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java @@ -5,19 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.none; import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.SqsAdviceBridge; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class SqsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -27,21 +21,9 @@ public SqsInstrumentationModule() { } @Override - public List typeInstrumentations() { - return singletonList(new DefaultSqsClientTypeInstrumentation()); - } - - public static class DefaultSqsClientTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - return named("software.amazon.awssdk.services.sqs.DefaultSqsClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isConstructor(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); - } + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); } @SuppressWarnings("unused") @@ -50,7 +32,7 @@ public static class RegisterAdvice { public static void onExit() { // (indirectly) using SqsImpl class here to make sure it is available from SqsAccess // (injected into app classloader) and checked by Muzzle - SqsAdviceBridge.init(); + SqsAdviceBridge.referenceForMuzzleOnly(); } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 56f43c7e1668..8ff9545dcb61 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -26,5 +26,6 @@ tasks { test { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) + systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java index 6efff826aa29..f1f8aad68dd1 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java @@ -8,12 +8,18 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; +import software.amazon.awssdk.services.sqs.model.SendMessageRequest; // helper class for calling methods that use sqs types in SqsImpl // if SqsImpl is not present these methods are no op final class SqsAccess { + private SqsAccess() {} + private static final boolean enabled = isSqsImplPresent(); private static boolean isSqsImplPresent() { @@ -31,14 +37,34 @@ private static boolean isSqsImplPresent() { } @NoMuzzle - static SdkRequest injectIntoSqsSendMessageRequest( + static boolean isSendMessageRequest(SdkRequest request) { + return enabled && request instanceof SendMessageRequest; + } + + @NoMuzzle + static SdkRequest injectIntoSendMessageRequest( TextMapPropagator messagingPropagator, SdkRequest rawRequest, io.opentelemetry.context.Context otelContext) { - if (!enabled) { - return rawRequest; - } - return SqsImpl.injectIntoSqsSendMessageRequest(messagingPropagator, rawRequest, otelContext); + assert enabled; // enabled checked already in instance check. + return SqsImpl.injectIntoSendMessageRequest(messagingPropagator, rawRequest, otelContext); + } + + @NoMuzzle + static boolean isReceiveMessageRequest(SdkRequest request) { + return enabled && request instanceof ReceiveMessageRequest; + } + + @NoMuzzle + public static SdkRequest modifyReceiveMessageRequest( + SdkRequest request, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { + assert enabled; // enabled checked already in instance check. + return SqsImpl.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); + } + + @NoMuzzle + static boolean isReceiveMessageResponse(SdkResponse response) { + return enabled && response instanceof ReceiveMessageResponse; } @NoMuzzle @@ -46,11 +72,7 @@ static void afterReceiveMessageExecution( TracingExecutionInterceptor config, Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (!enabled) { - return; - } - SqsImpl.afterConsumerResponse(config, executionAttributes, context); + assert enabled; // enabled checked already in instance check. + SqsImpl.afterReceiveMessageExecution(config, executionAttributes, context); } - - private SqsAccess() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java index 20eebbeac8e2..ddb3c05c5cbb 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java @@ -8,8 +8,8 @@ public final class SqsAdviceBridge { private SqsAdviceBridge() {} - public static void init() { - // called from advice - SqsImpl.init(); // Reference the actual, package-private, implementation class for Muzzle + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SqsImpl.class.getName() + " referencing for muzzle, should never be actually called"); } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java index 99c1f155450e..df3b402206f8 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java @@ -7,7 +7,9 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.interceptor.Context; @@ -15,6 +17,7 @@ import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; @@ -22,11 +25,7 @@ final class SqsImpl { private SqsImpl() {} - public static void init() { - // called from advice - } - - static SdkRequest injectIntoSqsSendMessageRequest( + static SdkRequest injectIntoSendMessageRequest( TextMapPropagator messagingPropagator, SdkRequest rawRequest, io.opentelemetry.context.Context otelContext) { @@ -48,7 +47,7 @@ static SdkRequest injectIntoSqsSendMessageRequest( } /** Create and close CONSUMER span for each message consumed. */ - static void afterConsumerResponse( + static void afterReceiveMessageExecution( TracingExecutionInterceptor config, ExecutionAttributes executionAttributes, Context.AfterExecution context) { @@ -91,4 +90,44 @@ private static void createConsumerSpan( consumerInstrumenter.end(context, executionAttributes, httpResponse, null); } } + + static SdkRequest modifyReceiveMessageRequest( + SdkRequest rawRequest, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { + ReceiveMessageRequest request = (ReceiveMessageRequest) rawRequest; + boolean hasXrayAttribute = true; + List existingAttributeNames = null; + if (useXrayPropagator) { + existingAttributeNames = request.attributeNamesAsStrings(); + hasXrayAttribute = + existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + + boolean hasMessageAttribute = true; + List existingMessageAttributeNames = null; + if (messagingPropagator != null) { + existingMessageAttributeNames = request.messageAttributeNames(); + hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); + } + + if (hasMessageAttribute && hasXrayAttribute) { + return request; + } + + ReceiveMessageRequest.Builder builder = request.toBuilder(); + if (!hasXrayAttribute) { + List attributeNames = new ArrayList<>(existingAttributeNames); + attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + builder.attributeNamesWithStrings(attributeNames); + } + if (messagingPropagator != null) { + List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); + for (String field : messagingPropagator.fields()) { + if (!existingMessageAttributeNames.contains(field)) { + messageAttributeNames.add(field); + } + } + builder.messageAttributeNames(messageAttributeNames); + } + return builder.build(); + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java deleted file mode 100644 index b6ac23ded0fe..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nullable; -import software.amazon.awssdk.core.SdkRequest; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - * - * @see SDK - * Javadoc - * @see Definition - * JSON - */ -final class SqsReceiveMessageRequestAccess { - - @Nullable private static final MethodHandle ATTRIBUTE_NAMES_WITH_STRINGS; - @Nullable private static final MethodHandle MESSAGE_ATTRIBUTE_NAMES; - - static { - Class receiveMessageRequestClass = null; - try { - receiveMessageRequestClass = - Class.forName("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest$Builder"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageRequestClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle withAttributeNames = null; - try { - withAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "attributeNamesWithStrings", - methodType(receiveMessageRequestClass, Collection.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - ATTRIBUTE_NAMES_WITH_STRINGS = withAttributeNames; - - MethodHandle messageAttributeNames = null; - try { - messageAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "messageAttributeNames", - methodType(receiveMessageRequestClass, Collection.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - MESSAGE_ATTRIBUTE_NAMES = messageAttributeNames; - } else { - ATTRIBUTE_NAMES_WITH_STRINGS = null; - MESSAGE_ATTRIBUTE_NAMES = null; - } - } - - static boolean isInstance(SdkRequest request) { - return request - .getClass() - .getName() - .equals("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest"); - } - - static void attributeNamesWithStrings(SdkRequest.Builder builder, List attributeNames) { - if (ATTRIBUTE_NAMES_WITH_STRINGS == null) { - return; - } - try { - ATTRIBUTE_NAMES_WITH_STRINGS.invoke(builder, attributeNames); - } catch (Throwable throwable) { - // Ignore - } - } - - static void messageAttributeNames( - SdkRequest.Builder builder, List messageAttributeNames) { - if (MESSAGE_ATTRIBUTE_NAMES == null) { - return; - } - try { - MESSAGE_ATTRIBUTE_NAMES.invoke(builder, messageAttributeNames); - } catch (Throwable throwable) { - // Ignore - } - } - - private SqsReceiveMessageRequestAccess() {} - - @SuppressWarnings({"rawtypes", "unchecked"}) - static List getAttributeNames(SdkRequest request) { - Optional optional = request.getValueForField("AttributeNames", List.class); - return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - static List getMessageAttributeNames(SdkRequest request) { - Optional optional = request.getValueForField("MessageAttributeNames", List.class); - return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); - } -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java deleted file mode 100644 index b863ef3ceaf2..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import software.amazon.awssdk.core.SdkRequest; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest for points where we are not - * sure whether SQS is on the classpath. - */ -final class SqsSendMessageRequestAccess { - static boolean isInstance(SdkRequest request) { - return request - .getClass() - .getName() - .equals("software.amazon.awssdk.services.sqs.model.SendMessageRequest"); - } - - private SqsSendMessageRequestAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index 95d81cd3034d..ccdc223ec5fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -15,7 +15,6 @@ import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import software.amazon.awssdk.awscore.AwsResponse; @@ -122,56 +121,17 @@ public SdkRequest modifyRequest( throw throwable; } - if (SqsReceiveMessageRequestAccess.isInstance(request)) { - return modifySqsReceiveMessageRequest(request); + if (SqsAccess.isReceiveMessageRequest(request)) { + return SqsAccess.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); } else if (messagingPropagator != null) { - if (SqsSendMessageRequestAccess.isInstance(request)) { - return SqsAccess.injectIntoSqsSendMessageRequest(messagingPropagator, request, otelContext); + if (SqsAccess.isSendMessageRequest(request)) { + return SqsAccess.injectIntoSendMessageRequest(messagingPropagator, request, otelContext); } // TODO: Support SendMessageBatchRequest (and thus SendMessageBatchRequestEntry) } return request; } - private SdkRequest modifySqsReceiveMessageRequest(SdkRequest request) { - boolean hasXrayAttribute = true; - List existingAttributeNames = null; - if (useXrayPropagator) { - existingAttributeNames = SqsReceiveMessageRequestAccess.getAttributeNames(request); - hasXrayAttribute = - existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - } - - boolean hasMessageAttribute = true; - List existingMessageAttributeNames = null; - if (messagingPropagator != null) { - existingMessageAttributeNames = - SqsReceiveMessageRequestAccess.getMessageAttributeNames(request); - hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); - } - - if (hasMessageAttribute && hasXrayAttribute) { - return request; - } - - SdkRequest.Builder builder = request.toBuilder(); - if (!hasXrayAttribute) { - List attributeNames = new ArrayList<>(existingAttributeNames); - attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - SqsReceiveMessageRequestAccess.attributeNamesWithStrings(builder, attributeNames); - } - if (messagingPropagator != null) { - List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); - for (String field : messagingPropagator.fields()) { - if (!existingMessageAttributeNames.contains(field)) { - messageAttributeNames.add(field); - } - } - SqsReceiveMessageRequestAccess.messageAttributeNames(builder, messageAttributeNames); - } - return builder.build(); - } - @Override public void afterMarshalling( Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { @@ -265,7 +225,7 @@ private void populateRequestAttributes( @Override public void afterExecution( Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (SqsReceiveMessageRequestAccess.isInstance(context.request())) { + if (SqsAccess.isReceiveMessageResponse(context.response())) { SqsAccess.afterReceiveMessageExecution(this, context, executionAttributes); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy index 211773859068..40a88e4c5863 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy @@ -15,7 +15,7 @@ class Aws2ClientTest extends AbstractAws2ClientTest implements LibraryTestTrait .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Default on in tests to cover more code + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) .build() .newExecutionInterceptor()) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index a3fcdca522da..3a10459e6dea 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -5,12 +5,14 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil import io.opentelemetry.instrumentation.test.InstrumentationSpecification import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import io.opentelemetry.testing.internal.armeria.common.HttpResponse import io.opentelemetry.testing.internal.armeria.common.HttpStatus import io.opentelemetry.testing.internal.armeria.common.MediaType import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension +import org.junit.jupiter.api.Assumptions import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.core.ResponseInputStream @@ -48,6 +50,7 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest +import software.amazon.awssdk.services.sqs.model.SendMessageRequest import spock.lang.Shared import spock.lang.Unroll @@ -317,7 +320,22 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { return AttributeValue.builder().s(value).build() } + def isSqsAttributeInjectionEnabled() { + // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor + return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) + } + + void assumeSupportedConfig(service, operation) { + Assumptions.assumeFalse( + service == "Sqs" + && operation == "SendMessage" + && isSqsAttributeInjectionEnabled(), + "Cannot check Sqs.SendMessage here due to hard-coded MD5.") + } + def "send #operation request with builder #builder.class.getName() mocked response"() { + assumeSupportedConfig(service, operation) + setup: configureSdkClient(builder) def client = builder @@ -384,6 +402,16 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ + "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE @@ -399,6 +427,7 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { } def "send #operation async request with builder #builder.class.getName() mocked response"() { + assumeSupportedConfig(service, operation) setup: configureSdkClient(builder) def client = builder @@ -465,6 +494,16 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ + "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index 7a7a96efefb0..a86c9bc8a81a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 import io.opentelemetry.instrumentation.test.InstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import org.elasticmq.rest.sqs.SQSRestServerBuilder import software.amazon.awssdk.auth.credentials.AwsBasicCredentials @@ -61,10 +60,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() def setupSpec() { - sqsPort = PortUtils.findOpenPort() - sqs = SQSRestServerBuilder.withPort(sqsPort).withInterface("localhost").start() + sqs = SQSRestServerBuilder.withPort(0).withInterface("localhost").start() + def server = sqs.waitUntilStarted() + sqsPort = server.localAddress().port println getClass().name + " SQS server started at: localhost:$sqsPort/" - } def cleanupSpec() { @@ -181,9 +180,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest) - client.receiveMessage(receiveMessageRequest) + def resp = client.receiveMessage(receiveMessageRequest) then: + resp.messages().size() == 1 assertSqsTraces() } @@ -198,9 +198,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest).get() - client.receiveMessage(receiveMessageRequest).get() + def resp = client.receiveMessage(receiveMessageRequest).get() then: + resp.messages().size() == 1 assertSqsTraces() } } 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 5d0cf18b17e1..6a31624a42bf 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 @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor; import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -66,6 +67,7 @@ public static void capture( .logRecordBuilder(); Map contextData = ThreadContext.getImmutableContext(); mapper.mapLogEvent(builder, message, level, marker, throwable, contextData); + builder.setTimestamp(Instant.now()); builder.emit(); } diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java index 4c753b52c4ee..612d9fce4cf5 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java @@ -8,6 +8,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.logs.Severity; @@ -16,6 +17,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.time.Instant; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -68,6 +70,8 @@ private static void test( String expectedSeverityText) throws InterruptedException { + Instant start = Instant.now(); + // when if (withParent) { testing.runWithSpan( @@ -88,6 +92,11 @@ private static void test( .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build()) .hasSeverity(expectedSeverity) .hasSeverityText(expectedSeverityText); + + assertThat(log.getTimestampEpochNanos()) + .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + if (logException) { assertThat(log) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java index f35564694fcb..535937e5fc3f 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java @@ -9,6 +9,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; @@ -24,7 +25,6 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.time.Instant; import java.util.List; -import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -107,8 +107,8 @@ void logWithExtras() { satisfies(SemanticAttributes.EXCEPTION_STACKTRACE, v -> v.contains("logWithExtras"))); assertThat(logDataList.get(0).getTimestampEpochNanos()) - .isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli())) - .isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); } @Test diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java index 0eff79c30dd2..24994d4d1972 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java @@ -34,6 +34,14 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) .and(takesArgument(1, named("com.mongodb.internal.async.SingleResultCallback"))), this.getClass().getName() + "$SingleResultCallbackArg1Advice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("selectServerAsync")) + .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) + .and(takesArgument(2, named("com.mongodb.internal.async.SingleResultCallback"))), + this.getClass().getName() + "$SingleResultCallbackArg2Advice"); } @SuppressWarnings("unused") @@ -45,4 +53,14 @@ public static void wrapCallback( callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); } } + + @SuppressWarnings("unused") + public static class SingleResultCallbackArg2Advice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapCallback( + @Advice.Argument(value = 2, readOnly = false) SingleResultCallback callback) { + callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); + } + } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java new file mode 100644 index 000000000000..a87070f926eb --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.function.Consumer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class DefaultConnectionPoolTaskInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("com.mongodb.internal.connection.DefaultConnectionPool$Task"); + } + + @Override + public void transform(TypeTransformer transformer) { + // outer class this is passed as arg 0 to constructor + transformer.applyAdviceToMethod( + isConstructor().and(takesArgument(2, Consumer.class)), + this.getClass().getName() + "$TaskAdvice"); + } + + @SuppressWarnings("unused") + public static class TaskAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapCallback( + @Advice.Argument(value = 2, readOnly = false) Consumer action) { + action = new TaskWrapper(Java8BytecodeBridge.currentContext(), action); + } + } +} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java index ecc578f7bced..25b8abe5c0c2 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java @@ -33,6 +33,7 @@ public List typeInstrumentations() { new InternalStreamConnectionInstrumentation(), new BaseClusterInstrumentation(), new DefaultConnectionPoolInstrumentation(), + new DefaultConnectionPoolTaskInstrumentation(), new AsyncWorkManagerInstrumentation()); } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java new file mode 100644 index 000000000000..ea4f72229143 --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.function.Consumer; + +public class TaskWrapper implements Consumer { + private final Context context; + private final Consumer delegate; + + public TaskWrapper(Context context, Consumer delegate) { + this.context = context; + this.delegate = delegate; + } + + @Override + public void accept(Object value) { + try (Scope ignored = context.makeCurrent()) { + delegate.accept(value); + } + } +} diff --git a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo index d707c05184d9..28af893acf71 100644 --- a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo @@ -1,5 +1,3 @@ -473 456 254:1 /docker/containers/be522444b60caf2d3934b8b24b916a8a314f4b68d4595aa419874657e8d103f2/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw -root@be522444b60c:/# cat /proc/self/mountinfo 456 375 0:143 / / rw,relatime master:175 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CBPR2ETR4Z3UMOOGIIRDVT2P27:/var/lib/docker/overlay2/l/46FCA2JFPCSNFGAR5TSYLLNHLK,upperdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/diff,workdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/work 457 456 0:146 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 466 456 0:147 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 diff --git a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo index 5d67bcdd1a60..6fb20214fc5d 100644 --- a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo @@ -1,5 +1,3 @@ -983 961 0:56 /containers/overlay-containers/2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6/userdata/hostname /etc/hostname ro,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=783888k,nr_inodes=195972,mode=700,uid=2024,gid=2024,inode64 -[root@2a33efc76e51 /]# cat /proc/self/mountinfo 961 812 0:58 / / ro,relatime - overlay overlay rw,lowerdir=/home/dracula/.local/share/containers/storage/overlay/l/4NB35A5Z4YGWDHXYEUZU4FN6BU,upperdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/diff,workdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/work,userxattr 962 961 0:63 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs rw 963 961 0:64 / /run rw,nosuid,nodev,relatime - tmpfs tmpfs rw,uid=2024,gid=2024,inode64 diff --git a/settings.gradle.kts b/settings.gradle.kts index f93c66b24642..e376d02ed66e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,7 @@ pluginManagement { } plugins { - id("com.gradle.enterprise") version "3.13.3" + id("com.gradle.enterprise") version "3.13.4" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11" id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } diff --git a/smoke-tests/images/fake-backend/build.gradle.kts b/smoke-tests/images/fake-backend/build.gradle.kts index 5baad1474ef0..0cb6d572d99a 100644 --- a/smoke-tests/images/fake-backend/build.gradle.kts +++ b/smoke-tests/images/fake-backend/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-grpc:1.24.0") + implementation("com.linecorp.armeria:armeria-grpc:1.24.1") implementation("io.opentelemetry.proto:opentelemetry-proto") runtimeOnly("org.slf4j:slf4j-simple") } diff --git a/testing/armeria-shaded-for-testing/build.gradle.kts b/testing/armeria-shaded-for-testing/build.gradle.kts index 481fd608e578..f34e1717a668 100644 --- a/testing/armeria-shaded-for-testing/build.gradle.kts +++ b/testing/armeria-shaded-for-testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-junit5:1.24.0") + implementation("com.linecorp.armeria:armeria-junit5:1.24.1") } tasks { From f2348eadf17a9465421d4c739122a7af6f02bf6d Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 28 Jun 2023 17:59:19 +0300 Subject: [PATCH 21/95] Fix exception when pulsar has multiple service addresses (#8816) --- .../instrumentation/pulsar/v2_8/UrlParserTest.java | 7 +++++++ .../pulsar/v2_8/ConsumerImplInstrumentation.java | 10 +++++----- .../pulsar/v2_8/MessageInstrumentation.java | 2 +- .../pulsar/v2_8/MessageListenerInstrumentation.java | 2 +- .../pulsar/v2_8/ProducerImplInstrumentation.java | 4 ++-- .../instrumentation/pulsar/v2_8/UrlParser.java | 9 +++++++-- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java index 06dce6643f18..bb109a99c6f6 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java @@ -16,6 +16,8 @@ public class UrlParserTest { void parseUrl() { assertThat(UrlParser.parseUrl(null)).isNull(); assertThat(UrlParser.parseUrl("localhost:1")).isNull(); + assertThat(UrlParser.parseUrl("localhost:1,localhost:2")).isNull(); + assertThat(UrlParser.parseUrl("localhost:1;localhost:2")).isNull(); { UrlData url = UrlParser.parseUrl("pulsar://localhost:1"); @@ -32,5 +34,10 @@ void parseUrl() { assertThat(url.getHost()).isEqualTo("localhost"); assertThat(url.getPort()).isNull(); } + { + UrlData url = UrlParser.parseUrl("pulsar://localhost:xxx"); + assertThat(url.getHost()).isEqualTo("localhost"); + assertThat(url.getPort()).isNull(); + } } } diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java index 25864824e9c0..0919440dc11e 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java @@ -88,12 +88,12 @@ public static void after( @SuppressWarnings("unused") public static class ConsumerInternalReceiveAdvice { - @Advice.OnMethodEnter + @Advice.OnMethodEnter(suppress = Throwable.class) public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, @@ -117,7 +117,7 @@ public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, @@ -137,7 +137,7 @@ public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, @@ -154,7 +154,7 @@ public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java index 1899a50151ce..f1ff1814b4f7 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java @@ -33,7 +33,7 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class MessageRecycleAdvice { - @Advice.OnMethodExit + @Advice.OnMethodExit(suppress = Throwable.class) public static void after(@Advice.This Message message) { // Clean context to prevent memory leak. VirtualFieldStore.inject(message, null); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java index a09187652ed1..d0b8dfe6a8ff 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java @@ -44,7 +44,7 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class ConsumerConfigurationDataMethodAdvice { - @Advice.OnMethodExit + @Advice.OnMethodExit(suppress = Throwable.class) public static void after( @Advice.This ConsumerConfigurationData data, @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java index 18525a63399d..4b782edd6e28 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java @@ -56,7 +56,7 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class ProducerImplConstructorAdvice { - @Advice.OnMethodExit + @Advice.OnMethodExit(suppress = Throwable.class) public static void intercept( @Advice.This ProducerImpl producer, @Advice.Argument(value = 0) PulsarClient client) { PulsarClientImpl pulsarClient = (PulsarClientImpl) client; @@ -69,7 +69,7 @@ public static void intercept( @SuppressWarnings("unused") public static class ProducerSendAsyncMethodAdvice { - @Advice.OnMethodEnter + @Advice.OnMethodEnter(suppress = Throwable.class) public static void before( @Advice.This ProducerImpl producer, @Advice.Argument(value = 0) Message message, diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java index 5e0087d766d1..cc04d9083f6f 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java @@ -10,7 +10,8 @@ public class UrlParser { private UrlParser() {} public static UrlData parseUrl(String url) { - if (url == null) { + // if there are multiple addresses then they are separated with , or ; + if (url == null || url.indexOf(',') != -1 || url.indexOf(';') != -1) { return null; } @@ -33,7 +34,11 @@ public static UrlData parseUrl(String url) { port = null; } else { host = authority.substring(0, portStart); - port = Integer.parseInt(authority.substring(portStart + 1)); + try { + port = Integer.parseInt(authority.substring(portStart + 1)); + } catch (NumberFormatException exception) { + port = null; + } } return new UrlData(host, port); From bb4211d0b7bf5215a55fbfd2da371691861e3dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Wed, 28 Jun 2023 20:04:06 +0200 Subject: [PATCH 22/95] Fix aws-sdk-2.2 library instrumentation crashing app when SQS is not on classpath (#8805) --- .../aws-sdk-2.2/javaagent/build.gradle.kts | 14 +- .../library-autoconfigure/build.gradle.kts | 14 +- .../aws-sdk-2.2/library/build.gradle.kts | 35 ++- .../awssdk/v2_2/PluginImplUtil.java | 49 +++ .../awssdk/v2_2/SqsAccess.java | 16 +- .../instrumentation/awssdk/v2_2/SqsImpl.java | 8 + .../awssdk/v2_2/Aws2ClientDynamodbTest.groovy | 22 ++ .../aws-sdk-2.2/testing/build.gradle.kts | 17 +- .../v2_2/AbstractAws2ClientCoreTest.groovy | 293 ++++++++++++++++++ .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 286 +---------------- 10 files changed, 422 insertions(+), 332 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index c33b3b1fa314..f5faf7d14c7c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -57,14 +57,12 @@ dependencies { testImplementation(project(":instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent")) testImplementation(project(":instrumentation:netty:netty-4.1:javaagent")) - latestDepTestLibrary("software.amazon.awssdk:aws-json-protocol:+") - latestDepTestLibrary("software.amazon.awssdk:aws-core:+") - latestDepTestLibrary("software.amazon.awssdk:dynamodb:+") - latestDepTestLibrary("software.amazon.awssdk:ec2:+") - latestDepTestLibrary("software.amazon.awssdk:kinesis:+") - latestDepTestLibrary("software.amazon.awssdk:rds:+") - latestDepTestLibrary("software.amazon.awssdk:s3:+") - latestDepTestLibrary("software.amazon.awssdk:sqs:+") + testLibrary("software.amazon.awssdk:dynamodb:2.2.0") + testLibrary("software.amazon.awssdk:ec2:2.2.0") + testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:rds:2.2.0") + testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:sqs:2.2.0") } tasks { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts index 6f3d3936ae3d..270ad6e23906 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts @@ -12,14 +12,12 @@ dependencies { testImplementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) - latestDepTestLibrary("software.amazon.awssdk:aws-core:+") - latestDepTestLibrary("software.amazon.awssdk:aws-json-protocol:+") - latestDepTestLibrary("software.amazon.awssdk:dynamodb:+") - latestDepTestLibrary("software.amazon.awssdk:ec2:+") - latestDepTestLibrary("software.amazon.awssdk:kinesis:+") - latestDepTestLibrary("software.amazon.awssdk:rds:+") - latestDepTestLibrary("software.amazon.awssdk:s3:+") - latestDepTestLibrary("software.amazon.awssdk:sqs:+") + testLibrary("software.amazon.awssdk:dynamodb:2.2.0") + testLibrary("software.amazon.awssdk:ec2:2.2.0") + testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:rds:2.2.0") + testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:sqs:2.2.0") } tasks { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 8ff9545dcb61..14559d2286c4 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -12,18 +12,35 @@ dependencies { testImplementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) - latestDepTestLibrary("software.amazon.awssdk:aws-core:+") - latestDepTestLibrary("software.amazon.awssdk:aws-json-protocol:+") - latestDepTestLibrary("software.amazon.awssdk:dynamodb:+") - latestDepTestLibrary("software.amazon.awssdk:ec2:+") - latestDepTestLibrary("software.amazon.awssdk:kinesis:+") - latestDepTestLibrary("software.amazon.awssdk:rds:+") - latestDepTestLibrary("software.amazon.awssdk:s3:+") - latestDepTestLibrary("software.amazon.awssdk:sqs:+") + testLibrary("software.amazon.awssdk:dynamodb:2.2.0") + testLibrary("software.amazon.awssdk:ec2:2.2.0") + testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:rds:2.2.0") + testLibrary("software.amazon.awssdk:s3:2.2.0") +} + +testing { + suites { + val testCoreOnly by registering(JvmTestSuite::class) { + sources { + groovy { + setSrcDirs(listOf("src/testCoreOnly/groovy")) + } + } + + dependencies { + implementation(project()) + implementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) + implementation("software.amazon.awssdk:aws-core:2.2.0") + implementation("software.amazon.awssdk:aws-json-protocol:2.2.0") + implementation("software.amazon.awssdk:dynamodb:2.2.0") + } + } + } } tasks { - test { + withType { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java new file mode 100644 index 000000000000..f5043ae13257 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import java.util.logging.Level; +import java.util.logging.Logger; + +final class PluginImplUtil { + private PluginImplUtil() {} + + private static final Logger logger = Logger.getLogger(PluginImplUtil.class.getName()); + + /** + * Check if the given {@code moduleNameImpl} is present. + * + *

For library instrumentations, the Impls will always be available but might fail to load if + * the corresponding SDK classes are not on the class path. For javaagent, the Impl is available + * only when the corresponding InstrumentationModule was successfully applied (muzzle passed). + * + *

Note that an present-but-incompatible library can only be detected by Muzzle. In + * library-mode, users need to ensure they are using a compatible SDK (component) versions + * themselves. + * + * @param implSimpleClassName The simple name of the impl class, e.g. {@code "SqsImpl"}. * + */ + static boolean isImplPresent(String implSimpleClassName) { + // We use getName().replace() instead of getPackage() because the latter is not guaranteed to + // work in all cases (e.g., we might be loaded into a custom classloader that doesn't handle it) + String implFullClassName = + PluginImplUtil.class.getName().replace(".PluginImplUtil", "." + implSimpleClassName); + try { + // using package name here because library instrumentation classes are relocated when embedded + // in the agent + Class.forName(implFullClassName); + return true; + } catch (ClassNotFoundException | LinkageError e) { + // ClassNotFoundException will happen when muzzle disabled us in javaagent mode; LinkageError + // (most likely NoClassDefFoundError) should happen when the class is loaded in library mode + // (where it is always available) but a dependency failed to load (most likely because the + // corresponding SDK dependency is not on the class path). + logger.log( + Level.FINE, e, () -> implFullClassName + " not present, probably incompatible version"); + return false; + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java index f1f8aad68dd1..22a41c4bbb41 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java @@ -20,21 +20,7 @@ final class SqsAccess { private SqsAccess() {} - private static final boolean enabled = isSqsImplPresent(); - - private static boolean isSqsImplPresent() { - try { - // for library instrumentation SqsImpl is always available - // for javaagent instrumentation SqsImpl is available only when SqsInstrumentationModule was - // successfully applied (muzzle passed) - // using package name here because library instrumentation classes are relocated when embedded - // in the agent - Class.forName(SqsAccess.class.getName().replace(".SqsAccess", ".SqsImpl")); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } + private static final boolean enabled = PluginImplUtil.isImplPresent("SqsImpl"); @NoMuzzle static boolean isSendMessageRequest(SdkRequest request) { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java index df3b402206f8..759d1eeb507b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java @@ -15,6 +15,7 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; @@ -23,6 +24,13 @@ // this class is only used from SqsAccess from method with @NoMuzzle annotation final class SqsImpl { + static { + // Force loading of SqsClient; this ensures that an exception is thrown at this point when the + // SQS library is not present, which will cause SqsAccess to have enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = SqsClient.class.getName(); + } + private SqsImpl() {} static SdkRequest injectIntoSendMessageRequest( diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy new file mode 100644 index 000000000000..55c33ddbeddf --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2 + +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration + +class Aws2ClientDynamodbTest extends AbstractAws2ClientCoreTest implements LibraryTestTrait { + @Override + ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder() + .addExecutionInterceptor( + AwsSdkTelemetry.builder(getOpenTelemetry()) + .setCaptureExperimentalSpanAttributes(true) + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) + .build() + .newExecutionInterceptor()) + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 6feb89ed47a4..4e85462c32ba 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -8,14 +8,15 @@ dependencies { api("software.amazon.awssdk:apache-client:2.2.0") // older versions don't play nice with armeria http server api("software.amazon.awssdk:netty-nio-client:2.11.0") - // When adding libraries, make sure to also add to the library/javaagent build files - // to ensure they are bumped to the latest version under testLatestDeps - api("software.amazon.awssdk:dynamodb:2.2.0") - api("software.amazon.awssdk:ec2:2.2.0") - api("software.amazon.awssdk:kinesis:2.2.0") - api("software.amazon.awssdk:rds:2.2.0") - api("software.amazon.awssdk:s3:2.2.0") - api("software.amazon.awssdk:sqs:2.2.0") + + // compileOnly because we never want to pin the low version implicitly; need to add dependencies + // explicitly in user projects, e.g. using testLatestDeps. + compileOnly("software.amazon.awssdk:dynamodb:2.2.0") + compileOnly("software.amazon.awssdk:ec2:2.2.0") + compileOnly("software.amazon.awssdk:kinesis:2.2.0") + compileOnly("software.amazon.awssdk:rds:2.2.0") + compileOnly("software.amazon.awssdk:s3:2.2.0") + compileOnly("software.amazon.awssdk:sqs:2.2.0") // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.12:1.0.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy new file mode 100644 index 000000000000..c270a6f38757 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy @@ -0,0 +1,293 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2 + +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import io.opentelemetry.testing.internal.armeria.common.HttpResponse +import io.opentelemetry.testing.internal.armeria.common.HttpStatus +import io.opentelemetry.testing.internal.armeria.common.MediaType +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.core.client.builder.SdkClientBuilder +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient +import software.amazon.awssdk.services.dynamodb.DynamoDbClient +import software.amazon.awssdk.services.dynamodb.model.* +import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension +import spock.lang.Shared +import spock.lang.Unroll + +import java.util.concurrent.Future + +import static com.google.common.collect.ImmutableMap.of +import static io.opentelemetry.api.trace.SpanKind.CLIENT + + +@Unroll +abstract class AbstractAws2ClientCoreTest extends InstrumentationSpecification { + def isSqsAttributeInjectionEnabled() { + // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor + return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) + } + + static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider + .create(AwsBasicCredentials.create("my-access-key", "my-secret-key")) + + @Shared + def server = new MockWebServerExtension() + + def setupSpec() { + server.start() + } + + def cleanupSpec() { + server.stop() + } + + def setup() { + server.beforeTestExecution(null) + } + + void configureSdkClient(SdkClientBuilder builder) { + builder.overrideConfiguration(createOverrideConfigurationBuilder().build()) + } + + abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder(); + + def "send DynamoDB #operation request with builder #builder.class.getName() mocked response"() { + setup: + configureSdkClient(builder) + def client = builder + .endpointOverride(server.httpUri()) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build() + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + expect: + response != null + response.class.simpleName.startsWith(operation) + switch (operation) { + case "CreateTable": + assertCreateTableRequest(path, method, requestId) + break + case "Query": + assertQueryRequest(path, method, requestId) + break + default: + assertDynamoDbRequest(service, operation, path, method, requestId) + } + + where: + [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbClient.builder()) + } + + def "send DynamoDB #operation async request with builder #builder.class.getName() mocked response"() { + setup: + configureSdkClient(builder) + def client = builder + .endpointOverride(server.httpUri()) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build() + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + expect: + response != null + switch (operation) { + case "CreateTable": + assertCreateTableRequest(path, method, requestId) + break + case "Query": + assertQueryRequest(path, method, requestId) + break + default: + assertDynamoDbRequest(service, operation, path, method, requestId) + } + + where: + [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbAsyncClient.builder()) + } + + def assertCreateTableRequest(path, method, requestId) { + assertTraces(1) { + trace(0, 1) { + span(0) { + name "DynamoDb.CreateTable" + kind CLIENT + hasNoParent() + attributes { + "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT" server.httpPort() + "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } + "$SemanticAttributes.HTTP_METHOD" "$method" + "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.RPC_SYSTEM" "aws-api" + "$SemanticAttributes.RPC_SERVICE" "DynamoDb" + "$SemanticAttributes.RPC_METHOD" "CreateTable" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + "aws.table.name" "sometable" + "$SemanticAttributes.DB_SYSTEM" "dynamodb" + "$SemanticAttributes.DB_OPERATION" "CreateTable" + "aws.dynamodb.global_secondary_indexes" "[{\"IndexName\":\"globalIndex\",\"KeySchema\":[{\"AttributeName\":\"attribute\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":10,\"WriteCapacityUnits\":12}},{\"IndexName\":\"globalIndexSecondary\",\"KeySchema\":[{\"AttributeName\":\"attributeSecondary\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":7,\"WriteCapacityUnits\":8}}]" + "aws.dynamodb.provisioned_throughput.read_capacity_units" "1" + "aws.dynamodb.provisioned_throughput.write_capacity_units" "1" + } + } + } + } + def request = server.takeRequest() + request.request().headers().get("X-Amzn-Trace-Id") != null + request.request().headers().get("traceparent") == null + } + + def assertQueryRequest(path, method, requestId) { + assertTraces(1) { + trace(0, 1) { + span(0) { + name "DynamoDb.Query" + kind CLIENT + hasNoParent() + attributes { + "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT" server.httpPort() + "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } + "$SemanticAttributes.HTTP_METHOD" "$method" + "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.RPC_SYSTEM" "aws-api" + "$SemanticAttributes.RPC_SERVICE" "DynamoDb" + "$SemanticAttributes.RPC_METHOD" "Query" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + "aws.table.name" "sometable" + "$SemanticAttributes.DB_SYSTEM" "dynamodb" + "$SemanticAttributes.DB_OPERATION" "Query" + "aws.dynamodb.limit" "10" + "aws.dynamodb.select" "ALL_ATTRIBUTES" + } + } + } + } + def request = server.takeRequest() + request.request().headers().get("X-Amzn-Trace-Id") != null + request.request().headers().get("traceparent") == null + } + + def assertDynamoDbRequest(service, operation, path, method, requestId) { + assertTraces(1) { + trace(0, 1) { + span(0) { + name "$service.$operation" + kind CLIENT + hasNoParent() + attributes { + "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT" server.httpPort() + "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } + "$SemanticAttributes.HTTP_METHOD" "$method" + "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.RPC_SYSTEM" "aws-api" + "$SemanticAttributes.RPC_SERVICE" "$service" + "$SemanticAttributes.RPC_METHOD" "${operation}" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + "aws.table.name" "sometable" + "$SemanticAttributes.DB_SYSTEM" "dynamodb" + "$SemanticAttributes.DB_OPERATION" "${operation}" + } + } + } + } + def request = server.takeRequest() + request.request().headers().get("X-Amzn-Trace-Id") != null + request.request().headers().get("traceparent") == null + } + + static dynamoDbRequestDataTable(client) { + [ + ["DynamoDb", "CreateTable", "POST", "/", "UNKNOWN", client, + { c -> c.createTable(createTableRequest()) }], + ["DynamoDb", "DeleteItem", "POST", "/", "UNKNOWN", client, + { c -> c.deleteItem(DeleteItemRequest.builder().tableName("sometable").key(of("anotherKey", val("value"), "key", val("value"))).conditionExpression("property in (:one :two)").build()) }], + ["DynamoDb", "DeleteTable", "POST", "/", "UNKNOWN", client, + { c -> c.deleteTable(DeleteTableRequest.builder().tableName("sometable").build()) }], + ["DynamoDb", "GetItem", "POST", "/", "UNKNOWN", client, + { c -> c.getItem(GetItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).attributesToGet("propertyOne", "propertyTwo").build()) }], + ["DynamoDb", "PutItem", "POST", "/", "UNKNOWN", client, + { c -> c.putItem(PutItemRequest.builder().tableName("sometable").item(of("key", val("value"), "attributeOne", val("one"), "attributeTwo", val("two"))).conditionExpression("attributeOne <> :someVal").build()) }], + ["DynamoDb", "Query", "POST", "/", "UNKNOWN", client, + { c -> c.query(QueryRequest.builder().tableName("sometable").select("ALL_ATTRIBUTES").keyConditionExpression("attribute = :aValue").filterExpression("anotherAttribute = :someVal").limit(10).build()) }], + ["DynamoDb", "UpdateItem", "POST", "/", "UNKNOWN", client, + { c -> c.updateItem(UpdateItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).conditionExpression("attributeOne <> :someVal").updateExpression("set attributeOne = :updateValue").build()) }] + ] + } + + static CreateTableRequest createTableRequest() { + return CreateTableRequest.builder() + .tableName("sometable") + .globalSecondaryIndexes(Arrays.asList( + GlobalSecondaryIndex.builder() + .indexName("globalIndex") + .keySchema( + KeySchemaElement.builder() + .attributeName("attribute") + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(10) + .writeCapacityUnits(12) + .build() + ) + .build(), + GlobalSecondaryIndex.builder() + .indexName("globalIndexSecondary") + .keySchema( + KeySchemaElement.builder() + .attributeName("attributeSecondary") + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(7) + .writeCapacityUnits(8) + .build() + ) + .build())) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(1) + .writeCapacityUnits(1) + .build() + ) + .build() + } + + static val(String value) { + return AttributeValue.builder().s(value).build() + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index 3a10459e6dea..6acb8980c0f8 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -5,37 +5,18 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil -import io.opentelemetry.instrumentation.test.InstrumentationSpecification + import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import io.opentelemetry.testing.internal.armeria.common.HttpResponse import io.opentelemetry.testing.internal.armeria.common.HttpStatus import io.opentelemetry.testing.internal.armeria.common.MediaType -import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension import org.junit.jupiter.api.Assumptions -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.core.ResponseInputStream import software.amazon.awssdk.core.async.AsyncResponseTransformer -import software.amazon.awssdk.core.client.builder.SdkClientBuilder -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration import software.amazon.awssdk.core.exception.SdkClientException import software.amazon.awssdk.core.retry.RetryPolicy import software.amazon.awssdk.http.apache.ApacheHttpClient import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient -import software.amazon.awssdk.services.dynamodb.DynamoDbClient -import software.amazon.awssdk.services.dynamodb.model.AttributeValue -import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest -import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest -import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest -import software.amazon.awssdk.services.dynamodb.model.GetItemRequest -import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput -import software.amazon.awssdk.services.dynamodb.model.PutItemRequest -import software.amazon.awssdk.services.dynamodb.model.QueryRequest -import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest import software.amazon.awssdk.services.ec2.Ec2AsyncClient import software.amazon.awssdk.services.ec2.Ec2Client import software.amazon.awssdk.services.kinesis.KinesisClient @@ -51,280 +32,17 @@ import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest import software.amazon.awssdk.services.sqs.model.SendMessageRequest -import spock.lang.Shared import spock.lang.Unroll import java.time.Duration import java.util.concurrent.Future -import static com.google.common.collect.ImmutableMap.of import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.api.trace.SpanKind.PRODUCER import static io.opentelemetry.api.trace.StatusCode.ERROR @Unroll -abstract class AbstractAws2ClientTest extends InstrumentationSpecification { - - private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider - .create(AwsBasicCredentials.create("my-access-key", "my-secret-key")) - - @Shared - def server = new MockWebServerExtension() - - def setupSpec() { - server.start() - } - - def cleanupSpec() { - server.stop() - } - - def setup() { - server.beforeTestExecution(null) - } - - void configureSdkClient(SdkClientBuilder builder) { - builder.overrideConfiguration(createOverrideConfigurationBuilder().build()) - } - - abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder(); - - def "send DynamoDB #operation request with builder #builder.class.getName() mocked response"() { - setup: - configureSdkClient(builder) - def client = builder - .endpointOverride(server.httpUri()) - .region(Region.AP_NORTHEAST_1) - .credentialsProvider(CREDENTIALS_PROVIDER) - .build() - server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) - def response = call.call(client) - - if (response instanceof Future) { - response = response.get() - } - - expect: - response != null - response.class.simpleName.startsWith(operation) - switch (operation) { - case "CreateTable": - assertCreateTableRequest(path, method, requestId) - break - case "Query": - assertQueryRequest(path, method, requestId) - break - default: - assertDynamoDbRequest(service, operation, path, method, requestId) - } - - where: - [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbClient.builder()) - } - - def "send DynamoDB #operation async request with builder #builder.class.getName() mocked response"() { - setup: - configureSdkClient(builder) - def client = builder - .endpointOverride(server.httpUri()) - .region(Region.AP_NORTHEAST_1) - .credentialsProvider(CREDENTIALS_PROVIDER) - .build() - server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) - def response = call.call(client) - - if (response instanceof Future) { - response = response.get() - } - - expect: - response != null - switch (operation) { - case "CreateTable": - assertCreateTableRequest(path, method, requestId) - break - case "Query": - assertQueryRequest(path, method, requestId) - break - default: - assertDynamoDbRequest(service, operation, path, method, requestId) - } - - where: - [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbAsyncClient.builder()) - } - - def assertCreateTableRequest(path, method, requestId) { - assertTraces(1) { - trace(0, 1) { - span(0) { - name "DynamoDb.CreateTable" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } - "$SemanticAttributes.HTTP_METHOD" "$method" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.RPC_SYSTEM" "aws-api" - "$SemanticAttributes.RPC_SERVICE" "DynamoDb" - "$SemanticAttributes.RPC_METHOD" "CreateTable" - "aws.agent" "java-aws-sdk" - "aws.requestId" "$requestId" - "aws.table.name" "sometable" - "$SemanticAttributes.DB_SYSTEM" "dynamodb" - "$SemanticAttributes.DB_OPERATION" "CreateTable" - "aws.dynamodb.global_secondary_indexes" "[{\"IndexName\":\"globalIndex\",\"KeySchema\":[{\"AttributeName\":\"attribute\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":10,\"WriteCapacityUnits\":12}},{\"IndexName\":\"globalIndexSecondary\",\"KeySchema\":[{\"AttributeName\":\"attributeSecondary\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":7,\"WriteCapacityUnits\":8}}]" - "aws.dynamodb.provisioned_throughput.read_capacity_units" "1" - "aws.dynamodb.provisioned_throughput.write_capacity_units" "1" - } - } - } - } - def request = server.takeRequest() - request.request().headers().get("X-Amzn-Trace-Id") != null - request.request().headers().get("traceparent") == null - } - - def assertQueryRequest(path, method, requestId) { - assertTraces(1) { - trace(0, 1) { - span(0) { - name "DynamoDb.Query" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } - "$SemanticAttributes.HTTP_METHOD" "$method" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.RPC_SYSTEM" "aws-api" - "$SemanticAttributes.RPC_SERVICE" "DynamoDb" - "$SemanticAttributes.RPC_METHOD" "Query" - "aws.agent" "java-aws-sdk" - "aws.requestId" "$requestId" - "aws.table.name" "sometable" - "$SemanticAttributes.DB_SYSTEM" "dynamodb" - "$SemanticAttributes.DB_OPERATION" "Query" - "aws.dynamodb.limit" "10" - "aws.dynamodb.select" "ALL_ATTRIBUTES" - } - } - } - } - def request = server.takeRequest() - request.request().headers().get("X-Amzn-Trace-Id") != null - request.request().headers().get("traceparent") == null - } - - def assertDynamoDbRequest(service, operation, path, method, requestId) { - assertTraces(1) { - trace(0, 1) { - span(0) { - name "$service.$operation" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } - "$SemanticAttributes.HTTP_METHOD" "$method" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.RPC_SYSTEM" "aws-api" - "$SemanticAttributes.RPC_SERVICE" "$service" - "$SemanticAttributes.RPC_METHOD" "${operation}" - "aws.agent" "java-aws-sdk" - "aws.requestId" "$requestId" - "aws.table.name" "sometable" - "$SemanticAttributes.DB_SYSTEM" "dynamodb" - "$SemanticAttributes.DB_OPERATION" "${operation}" - } - } - } - } - def request = server.takeRequest() - request.request().headers().get("X-Amzn-Trace-Id") != null - request.request().headers().get("traceparent") == null - } - - static dynamoDbRequestDataTable(client) { - [ - ["DynamoDb", "CreateTable", "POST", "/", "UNKNOWN", client, - { c -> c.createTable(createTableRequest()) }], - ["DynamoDb", "DeleteItem", "POST", "/", "UNKNOWN", client, - { c -> c.deleteItem(DeleteItemRequest.builder().tableName("sometable").key(of("anotherKey", val("value"), "key", val("value"))).conditionExpression("property in (:one :two)").build()) }], - ["DynamoDb", "DeleteTable", "POST", "/", "UNKNOWN", client, - { c -> c.deleteTable(DeleteTableRequest.builder().tableName("sometable").build()) }], - ["DynamoDb", "GetItem", "POST", "/", "UNKNOWN", client, - { c -> c.getItem(GetItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).attributesToGet("propertyOne", "propertyTwo").build()) }], - ["DynamoDb", "PutItem", "POST", "/", "UNKNOWN", client, - { c -> c.putItem(PutItemRequest.builder().tableName("sometable").item(of("key", val("value"), "attributeOne", val("one"), "attributeTwo", val("two"))).conditionExpression("attributeOne <> :someVal").build()) }], - ["DynamoDb", "Query", "POST", "/", "UNKNOWN", client, - { c -> c.query(QueryRequest.builder().tableName("sometable").select("ALL_ATTRIBUTES").keyConditionExpression("attribute = :aValue").filterExpression("anotherAttribute = :someVal").limit(10).build()) }], - ["DynamoDb", "UpdateItem", "POST", "/", "UNKNOWN", client, - { c -> c.updateItem(UpdateItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).conditionExpression("attributeOne <> :someVal").updateExpression("set attributeOne = :updateValue").build()) }] - ] - } - - static CreateTableRequest createTableRequest() { - return CreateTableRequest.builder() - .tableName("sometable") - .globalSecondaryIndexes(Arrays.asList( - GlobalSecondaryIndex.builder() - .indexName("globalIndex") - .keySchema( - KeySchemaElement.builder() - .attributeName("attribute") - .build()) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(10) - .writeCapacityUnits(12) - .build() - ) - .build(), - GlobalSecondaryIndex.builder() - .indexName("globalIndexSecondary") - .keySchema( - KeySchemaElement.builder() - .attributeName("attributeSecondary") - .build()) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(7) - .writeCapacityUnits(8) - .build() - ) - .build())) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(1) - .writeCapacityUnits(1) - .build() - ) - .build() - } - - static val(String value) { - return AttributeValue.builder().s(value).build() - } - - def isSqsAttributeInjectionEnabled() { - // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor - return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) - } - +abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { void assumeSupportedConfig(service, operation) { Assumptions.assumeFalse( service == "Sqs" From 91a5cce03c58343d5d0873044881238ca8f0bfb7 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 28 Jun 2023 20:38:55 +0200 Subject: [PATCH 23/95] Remove `server.{address,port}` from HTTP server metrics (#8771) --- .../http/TemporaryMetricsView.java | 6 ++--- .../http/TemporaryMetricsViewTest.java | 8 ++---- .../HttpServerMetricsStableSemconvTest.java | 25 +++++-------------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java index b340e9871faf..8a2f7cc7483c 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java @@ -39,8 +39,6 @@ private static Set buildDurationAlwaysInclude() { view.add(HttpAttributes.HTTP_RESPONSE_STATUS_CODE); view.add(NetworkAttributes.NETWORK_PROTOCOL_NAME); view.add(NetworkAttributes.NETWORK_PROTOCOL_VERSION); - view.add(NetworkAttributes.SERVER_ADDRESS); - view.add(NetworkAttributes.SERVER_PORT); return view; } @@ -54,6 +52,8 @@ private static Set buildDurationClientView() { view.add(SemanticAttributes.NET_SOCK_PEER_ADDR); // stable semconv view.add(NetworkAttributes.SERVER_SOCKET_ADDRESS); + view.add(NetworkAttributes.SERVER_ADDRESS); + view.add(NetworkAttributes.SERVER_PORT); return view; } @@ -83,8 +83,6 @@ private static Set buildActiveRequestsView() { view.add(SemanticAttributes.NET_HOST_PORT); // stable semconv view.add(HttpAttributes.HTTP_REQUEST_METHOD); - view.add(NetworkAttributes.SERVER_ADDRESS); - view.add(NetworkAttributes.SERVER_PORT); view.add(UrlAttributes.URL_SCHEME); return view; } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java index a5968c913718..e08ba3778630 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java @@ -174,9 +174,7 @@ void shouldApplyServerDurationAndSizeView_stableSemconv() { entry(SemanticAttributes.HTTP_ROUTE, "/somehost/high/{name}/{id}"), entry(UrlAttributes.URL_SCHEME, "https"), entry(NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), - entry(NetworkAttributes.NETWORK_PROTOCOL_VERSION, "1.1"), - entry(NetworkAttributes.SERVER_ADDRESS, "somehost"), - entry(NetworkAttributes.SERVER_PORT, 443L)); + entry(NetworkAttributes.NETWORK_PROTOCOL_VERSION, "1.1")); } @Test @@ -234,8 +232,6 @@ void shouldApplyActiveRequestsView_stableSemconv() { assertThat(applyActiveRequestsView(attributes)) .containsOnly( entry(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - entry(UrlAttributes.URL_SCHEME, "https"), - entry(NetworkAttributes.SERVER_ADDRESS, "somehost"), - entry(NetworkAttributes.SERVER_PORT, 443L)); + entry(UrlAttributes.URL_SCHEME, "https")); } } diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java index 4bdb7fe7e10f..c1ee03986b4b 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java @@ -93,9 +93,7 @@ void collectsMetrics() { .hasValue(1) .hasAttributesSatisfying( equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -118,9 +116,7 @@ void collectsMetrics() { .hasValue(2) .hasAttributesSatisfying( equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -142,9 +138,7 @@ void collectsMetrics() { .hasValue(1) .hasAttributesSatisfying( equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -167,9 +161,7 @@ void collectsMetrics() { NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), equalTo( NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -193,9 +185,7 @@ void collectsMetrics() { NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), equalTo( NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -218,9 +208,7 @@ void collectsMetrics() { NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), equalTo( NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -328,7 +316,6 @@ void collectsHttpRouteFromEndAttributes() { .hasSum(0.100 /* seconds */) .hasAttributesSatisfying( equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "host"), equalTo( SemanticAttributes.HTTP_ROUTE, "/test/{id}"))))); } From eec870318889574d036eb54ddf5761f9abdba57a Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 28 Jun 2023 20:43:07 +0200 Subject: [PATCH 24/95] Rename common HTTP configuration settings (#8758) --- .../bootstrap/internal/CommonConfig.java | 24 +++++++++++++++---- .../internal/DeprecatedConfigProperties.java | 18 ++++++++++++-- ...CapturedHttpHeadersTestConfigSupplier.java | 8 +++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java index 7072f64809c9..799086a31e51 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.bootstrap.internal; -import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import java.util.List; @@ -33,14 +32,29 @@ public static CommonConfig get() { CommonConfig(InstrumentationConfig config) { peerServiceMapping = config.getMap("otel.instrumentation.common.peer-service-mapping", emptyMap()); + + // TODO (mateusz): remove the old config names in 2.0 clientRequestHeaders = - config.getList("otel.instrumentation.http.capture-headers.client.request", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.client.request", + "otel.instrumentation.http.client.capture-request-headers"); clientResponseHeaders = - config.getList("otel.instrumentation.http.capture-headers.client.response", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.client.response", + "otel.instrumentation.http.client.capture-response-headers"); serverRequestHeaders = - config.getList("otel.instrumentation.http.capture-headers.server.request", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.server.request", + "otel.instrumentation.http.server.capture-request-headers"); serverResponseHeaders = - config.getList("otel.instrumentation.http.capture-headers.server.response", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.server.response", + "otel.instrumentation.http.server.capture-response-headers"); + statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); } diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java index 3f9e2a7f8cf6..f32a5d796273 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java @@ -5,8 +5,10 @@ package io.opentelemetry.javaagent.bootstrap.internal; +import static java.util.Collections.emptyList; import static java.util.logging.Level.WARNING; +import java.util.List; import java.util.logging.Logger; /** @@ -22,14 +24,26 @@ public static boolean getBoolean( String deprecatedPropertyName, String newPropertyName, boolean defaultValue) { + warnIfUsed(config, deprecatedPropertyName, newPropertyName); + boolean value = config.getBoolean(deprecatedPropertyName, defaultValue); + return config.getBoolean(newPropertyName, value); + } + + public static List getList( + InstrumentationConfig config, String deprecatedPropertyName, String newPropertyName) { + warnIfUsed(config, deprecatedPropertyName, newPropertyName); + List value = config.getList(deprecatedPropertyName, emptyList()); + return config.getList(newPropertyName, value); + } + + private static void warnIfUsed( + InstrumentationConfig config, String deprecatedPropertyName, String newPropertyName) { if (config.getString(deprecatedPropertyName) != null) { logger.log( WARNING, "Deprecated property \"{0}\" was used; use the \"{1}\" property instead", new Object[] {deprecatedPropertyName, newPropertyName}); } - boolean value = config.getBoolean(deprecatedPropertyName, defaultValue); - return config.getBoolean(newPropertyName, value); } private DeprecatedConfigProperties() {} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java index 2485e32f9506..dce4ac5701c6 100644 --- a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java @@ -22,10 +22,10 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { private static Map getTestProperties() { Map testConfig = new HashMap<>(); - testConfig.put("otel.instrumentation.http.capture-headers.client.request", "X-Test-Request"); - testConfig.put("otel.instrumentation.http.capture-headers.client.response", "X-Test-Response"); - testConfig.put("otel.instrumentation.http.capture-headers.server.request", "X-Test-Request"); - testConfig.put("otel.instrumentation.http.capture-headers.server.response", "X-Test-Response"); + testConfig.put("otel.instrumentation.http.client.capture-request-headers", "X-Test-Request"); + testConfig.put("otel.instrumentation.http.client.capture-response-headers", "X-Test-Response"); + testConfig.put("otel.instrumentation.http.server.capture-request-headers", "X-Test-Request"); + testConfig.put("otel.instrumentation.http.server.capture-response-headers", "X-Test-Response"); return testConfig; } } From b8bcbffdb74291c30d9bf6dc0ddadf9434f12df6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:56:30 -0700 Subject: [PATCH 25/95] Bump com.linecorp.armeria:armeria-grpc from 1.24.1 to 1.24.2 (#8825) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/images/fake-backend/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/images/fake-backend/build.gradle.kts b/smoke-tests/images/fake-backend/build.gradle.kts index 0cb6d572d99a..ca853899c07d 100644 --- a/smoke-tests/images/fake-backend/build.gradle.kts +++ b/smoke-tests/images/fake-backend/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-grpc:1.24.1") + implementation("com.linecorp.armeria:armeria-grpc:1.24.2") implementation("io.opentelemetry.proto:opentelemetry-proto") runtimeOnly("org.slf4j:slf4j-simple") } From 71c4db7d80367348a6b22e8b6bf7065e9d406867 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:56:44 -0700 Subject: [PATCH 26/95] Bump io.quarkus.platform:quarkus-bom from 3.1.2.Final to 3.1.3.Final (#8824) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/images/quarkus/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/images/quarkus/build.gradle.kts b/smoke-tests/images/quarkus/build.gradle.kts index 3a2b9651c42f..f236c3b2118b 100644 --- a/smoke-tests/images/quarkus/build.gradle.kts +++ b/smoke-tests/images/quarkus/build.gradle.kts @@ -16,7 +16,7 @@ plugins { } dependencies { - implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.1.2.Final")) + implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.1.3.Final")) implementation("io.quarkus:quarkus-resteasy") } From 509448fb13ffe99c200ad17efd88488e6d1a8c6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:58:28 -0700 Subject: [PATCH 27/95] Bump com.github.jk1.dependency-license-report from 2.4 to 2.5 (#8823) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index e376d02ed66e..cdb637ca3426 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.bmuschko.docker-remote-api") version "8.1.0" id("com.github.ben-manes.versions") version "0.47.0" - id("com.github.jk1.dependency-license-report") version "2.4" + id("com.github.jk1.dependency-license-report") version "2.5" id("com.google.cloud.tools.jib") version "3.3.2" id("com.gradle.plugin-publish") version "1.2.0" id("io.github.gradle-nexus.publish-plugin") version "1.3.0" From 2c4a283e31eba08589a4cfa75ad3f83831f53039 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:59:14 -0700 Subject: [PATCH 28/95] Bump autoValueVersion from 1.10.1 to 1.10.2 (#8822) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index c2eb71beb3f5..4089230e94d8 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -41,7 +41,7 @@ val DEPENDENCY_BOMS = listOf( ) val autoServiceVersion = "1.1.1" -val autoValueVersion = "1.10.1" +val autoValueVersion = "1.10.2" val errorProneVersion = "2.19.1" val byteBuddyVersion = "1.14.5" val asmVersion = "9.5" From 4bc4189ef5eb255f4505d4ea847b4367b3fcccf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:59:52 -0700 Subject: [PATCH 29/95] Bump com.linecorp.armeria:armeria-junit5 from 1.24.1 to 1.24.2 (#8820) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/armeria-shaded-for-testing/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/armeria-shaded-for-testing/build.gradle.kts b/testing/armeria-shaded-for-testing/build.gradle.kts index f34e1717a668..ef67bf775253 100644 --- a/testing/armeria-shaded-for-testing/build.gradle.kts +++ b/testing/armeria-shaded-for-testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-junit5:1.24.1") + implementation("com.linecorp.armeria:armeria-junit5:1.24.2") } tasks { From 3b069a77d3b3b8c667a35f1330d8ded69343a853 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 29 Jun 2023 10:12:03 +0300 Subject: [PATCH 30/95] Rename daily no cache build (#8827) --- .github/workflows/build-daily-no-build-cache.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-daily-no-build-cache.yml b/.github/workflows/build-daily-no-build-cache.yml index 373b118162c2..5517e0426ee2 100644 --- a/.github/workflows/build-daily-no-build-cache.yml +++ b/.github/workflows/build-daily-no-build-cache.yml @@ -1,4 +1,4 @@ -name: Build (daily, --no-build-cache) +name: Build (daily --no-build-cache) on: schedule: From 1110eac42754158f895e3f6cd1d5d78e0709064e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Prinet?= Date: Thu, 29 Jun 2023 14:49:01 +0200 Subject: [PATCH 31/95] Upgrade playframework Gradle plugin to fix a build cache miss (#8828) --- smoke-tests/images/play/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/images/play/build.gradle.kts b/smoke-tests/images/play/build.gradle.kts index 76cd89f7320a..9794d9d1093b 100644 --- a/smoke-tests/images/play/build.gradle.kts +++ b/smoke-tests/images/play/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("otel.spotless-conventions") id("com.google.cloud.tools.jib") - id("org.gradle.playframework") version "0.13" + id("org.gradle.playframework") version "0.14" } val playVer = "2.8.19" From eaf11ab2f397353329dc31597ff5b2cc7a89783e Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 29 Jun 2023 10:51:45 -0400 Subject: [PATCH 32/95] Update CONTRIBUTING.md to document Java 17 requirement. (#8819) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be1585073c64..3011014c6539 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ and discuss your ideas or propose the changes you wish to make. ## Building -In order to build and test this whole repository you need JDK 17 or higher. +This project requires Java 17 to build and run tests. Newer JDK's may work, but this version is used in CI. Some instrumentations and tests may put constraints on which java versions they support. See [Running the tests](./docs/contributing/running-tests.md) for more details. @@ -22,7 +22,7 @@ the Sonatype OSS snapshots repository at `https://oss.sonatype.org/content/repos ### Building from source -Build using Java 17+: +Build using Java 17: ```bash java -version From a1f691798e4f2cfba7742ed91ca13488c8a8c6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Thu, 29 Jun 2023 16:55:11 +0200 Subject: [PATCH 33/95] aws-sdk-2.2.: Support injection into SQS.SendMessageBatch message attributes (#8798) Co-authored-by: Mateusz Rzeszutek --- .../aws-sdk-2.2/library/build.gradle.kts | 3 + .../awssdk/v2_2/SqsAccess.java | 57 ++---- .../instrumentation/awssdk/v2_2/SqsImpl.java | 122 +++++++++--- .../v2_2/TracingExecutionInterceptor.java | 20 +- .../awssdk/v2_2/Aws2SqsTracingTest.groovy | 5 + ...Aws2SqsTracingTestWithW3CPropagator.groovy | 14 +- ...tWithW3CPropagatorAndXrayPropagator.groovy | 7 +- .../v2_2/AbstractAws2ClientCoreTest.groovy | 2 +- .../v2_2/AbstractAws2SqsTracingTest.groovy | 173 +++++++++++++++++- 9 files changed, 319 insertions(+), 84 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 14559d2286c4..94e6c648e138 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -42,6 +42,9 @@ testing { tasks { withType { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + + // NB: If you'd like to change these, there is some cleanup work to be done, as most tests ignore this and + // set the value directly (the "library" does not normally query it, only library-autoconfigure) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java index 22a41c4bbb41..06c7f343e213 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java @@ -7,13 +7,10 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; +import javax.annotation.Nullable; import software.amazon.awssdk.core.SdkRequest; -import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; -import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; -import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; -import software.amazon.awssdk.services.sqs.model.SendMessageRequest; // helper class for calling methods that use sqs types in SqsImpl // if SqsImpl is not present these methods are no op @@ -23,42 +20,26 @@ private SqsAccess() {} private static final boolean enabled = PluginImplUtil.isImplPresent("SqsImpl"); @NoMuzzle - static boolean isSendMessageRequest(SdkRequest request) { - return enabled && request instanceof SendMessageRequest; - } - - @NoMuzzle - static SdkRequest injectIntoSendMessageRequest( - TextMapPropagator messagingPropagator, - SdkRequest rawRequest, - io.opentelemetry.context.Context otelContext) { - assert enabled; // enabled checked already in instance check. - return SqsImpl.injectIntoSendMessageRequest(messagingPropagator, rawRequest, otelContext); - } - - @NoMuzzle - static boolean isReceiveMessageRequest(SdkRequest request) { - return enabled && request instanceof ReceiveMessageRequest; - } - - @NoMuzzle - public static SdkRequest modifyReceiveMessageRequest( - SdkRequest request, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { - assert enabled; // enabled checked already in instance check. - return SqsImpl.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); - } - - @NoMuzzle - static boolean isReceiveMessageResponse(SdkResponse response) { - return enabled && response instanceof ReceiveMessageResponse; + static boolean afterReceiveMessageExecution( + Context.AfterExecution context, + ExecutionAttributes executionAttributes, + TracingExecutionInterceptor config) { + return enabled && SqsImpl.afterReceiveMessageExecution(context, executionAttributes, config); } + /** + * Returns {@code null} (not the unmodified {@code request}!) if nothing matched, so that other + * handling can be tried. + */ + @Nullable @NoMuzzle - static void afterReceiveMessageExecution( - TracingExecutionInterceptor config, - Context.AfterExecution context, - ExecutionAttributes executionAttributes) { - assert enabled; // enabled checked already in instance check. - SqsImpl.afterReceiveMessageExecution(config, executionAttributes, context); + static SdkRequest modifyRequest( + SdkRequest request, + io.opentelemetry.context.Context otelContext, + boolean useXrayPropagator, + TextMapPropagator messagingPropagator) { + return enabled + ? SqsImpl.modifyRequest(request, otelContext, useXrayPropagator, messagingPropagator) + : null; } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java index 759d1eeb507b..587a5b919ff3 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java @@ -11,7 +11,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.http.SdkHttpResponse; @@ -20,6 +22,8 @@ import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; // this class is only used from SqsAccess from method with @NoMuzzle annotation @@ -33,44 +37,30 @@ final class SqsImpl { private SqsImpl() {} - static SdkRequest injectIntoSendMessageRequest( - TextMapPropagator messagingPropagator, - SdkRequest rawRequest, - io.opentelemetry.context.Context otelContext) { - SendMessageRequest request = (SendMessageRequest) rawRequest; - Map messageAttributes = - new HashMap<>(request.messageAttributes()); - - messagingPropagator.inject( - otelContext, - messageAttributes, - (carrier, k, v) -> { - carrier.put(k, MessageAttributeValue.builder().stringValue(v).dataType("String").build()); - }); + static boolean afterReceiveMessageExecution( + Context.AfterExecution context, + ExecutionAttributes executionAttributes, + TracingExecutionInterceptor config) { - if (messageAttributes.size() > 10) { // Too many attributes, we don't want to break the call. - return request; + SdkResponse rawResponse = context.response(); + if (!(rawResponse instanceof ReceiveMessageResponse)) { + return false; } - return request.toBuilder().messageAttributes(messageAttributes).build(); - } - /** Create and close CONSUMER span for each message consumed. */ - static void afterReceiveMessageExecution( - TracingExecutionInterceptor config, - ExecutionAttributes executionAttributes, - Context.AfterExecution context) { - ReceiveMessageResponse response = (ReceiveMessageResponse) context.response(); + ReceiveMessageResponse response = (ReceiveMessageResponse) rawResponse; SdkHttpResponse httpResponse = context.httpResponse(); for (Message message : response.messages()) { - createConsumerSpan(config, message, executionAttributes, httpResponse); + createConsumerSpan(message, httpResponse, executionAttributes, config); } + + return true; } private static void createConsumerSpan( - TracingExecutionInterceptor config, Message message, + SdkHttpResponse httpResponse, ExecutionAttributes executionAttributes, - SdkHttpResponse httpResponse) { + TracingExecutionInterceptor config) { io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.root(); @@ -99,9 +89,81 @@ private static void createConsumerSpan( } } - static SdkRequest modifyReceiveMessageRequest( - SdkRequest rawRequest, boolean useXrayPropagator, TextMapPropagator messagingPropagator) { - ReceiveMessageRequest request = (ReceiveMessageRequest) rawRequest; + @Nullable + static SdkRequest modifyRequest( + SdkRequest request, + io.opentelemetry.context.Context otelContext, + boolean useXrayPropagator, + TextMapPropagator messagingPropagator) { + if (request instanceof ReceiveMessageRequest) { + return modifyReceiveMessageRequest( + (ReceiveMessageRequest) request, useXrayPropagator, messagingPropagator); + } else if (messagingPropagator != null) { + if (request instanceof SendMessageRequest) { + return injectIntoSendMessageRequest( + (SendMessageRequest) request, otelContext, messagingPropagator); + } else if (request instanceof SendMessageBatchRequest) { + return injectIntoSendMessageBatchRequest( + (SendMessageBatchRequest) request, otelContext, messagingPropagator); + } + } + return null; + } + + private static SdkRequest injectIntoSendMessageBatchRequest( + SendMessageBatchRequest request, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + ArrayList entries = new ArrayList<>(request.entries()); + for (int i = 0; i < entries.size(); ++i) { + SendMessageBatchRequestEntry entry = entries.get(i); + Map messageAttributes = + new HashMap<>(entry.messageAttributes()); + + // TODO: Per https://github.com/open-telemetry/oteps/pull/220, each message should get + // a separate context. We don't support this yet, also because it would be inconsistent + // with the header-based X-Ray propagation + // (probably could override it here by setting the X-Ray message system attribute) + if (injectIntoMessageAttributes(messageAttributes, otelContext, messagingPropagator)) { + entries.set(i, entry.toBuilder().messageAttributes(messageAttributes).build()); + } + } + return request.toBuilder().entries(entries).build(); + } + + private static SdkRequest injectIntoSendMessageRequest( + SendMessageRequest request, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + Map messageAttributes = + new HashMap<>(request.messageAttributes()); + if (!injectIntoMessageAttributes(messageAttributes, otelContext, messagingPropagator)) { + return request; + } + return request.toBuilder().messageAttributes(messageAttributes).build(); + } + + private static boolean injectIntoMessageAttributes( + Map messageAttributes, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + messagingPropagator.inject( + otelContext, + messageAttributes, + (carrier, k, v) -> { + carrier.put(k, MessageAttributeValue.builder().stringValue(v).dataType("String").build()); + }); + + // Return whether the injection resulted in an attribute count that is still supported. + // See + // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes + return messageAttributes.size() <= 10; + } + + private static SdkRequest modifyReceiveMessageRequest( + ReceiveMessageRequest request, + boolean useXrayPropagator, + TextMapPropagator messagingPropagator) { boolean hasXrayAttribute = true; List existingAttributeNames = null; if (useXrayPropagator) { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index ccdc223ec5fc..ed2c9bae8b5e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -121,14 +121,14 @@ public SdkRequest modifyRequest( throw throwable; } - if (SqsAccess.isReceiveMessageRequest(request)) { - return SqsAccess.modifyReceiveMessageRequest(request, useXrayPropagator, messagingPropagator); - } else if (messagingPropagator != null) { - if (SqsAccess.isSendMessageRequest(request)) { - return SqsAccess.injectIntoSendMessageRequest(messagingPropagator, request, otelContext); - } - // TODO: Support SendMessageBatchRequest (and thus SendMessageBatchRequestEntry) + SdkRequest sqsModifiedRequest = + SqsAccess.modifyRequest(request, otelContext, useXrayPropagator, messagingPropagator); + if (sqsModifiedRequest != null) { + return sqsModifiedRequest; } + + // Insert other special handling here, following the same pattern as SQS. + return request; } @@ -225,9 +225,9 @@ private void populateRequestAttributes( @Override public void afterExecution( Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (SqsAccess.isReceiveMessageResponse(context.response())) { - SqsAccess.afterReceiveMessageExecution(this, context, executionAttributes); - } + + // Other special handling could be shortcut-&&ed after this (false is returned if not handled). + SqsAccess.afterReceiveMessageExecution(context, executionAttributes, this); io.opentelemetry.context.Context otelContext = getContext(executionAttributes); if (otelContext != null) { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy index 3aadab3cbc49..236f846fa85f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy @@ -18,4 +18,9 @@ class Aws2SqsTracingTest extends AbstractAws2SqsTracingTest implements LibraryTe .build() .newExecutionInterceptor()) } + + @Override + boolean isSqsAttributeInjectionEnabled() { + false + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy index 4099b0ae91a6..b06a7a354596 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy @@ -15,9 +15,19 @@ class Aws2SqsTracingTestWithW3CPropagator extends AbstractAws2SqsTracingTest imp .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Difference to main test - .setUseXrayPropagator(false) // Disable to confirm messaging propagator actually works + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) // Difference to main test + .setUseXrayPropagator(isXrayInjectionEnabled()) // Disable to confirm messaging propagator actually works .build() .newExecutionInterceptor()) } + + @Override + boolean isSqsAttributeInjectionEnabled() { + true + } + + @Override + boolean isXrayInjectionEnabled() { + false + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy index a4af1e1b56d9..e6b5ddc3e598 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy @@ -16,8 +16,13 @@ class Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator extends AbstractAws2S .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Difference to main test + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) // Difference to main test .build() .newExecutionInterceptor()) } + + @Override + boolean isSqsAttributeInjectionEnabled() { + true + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy index c270a6f38757..6463c400a3e5 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy @@ -31,7 +31,7 @@ import static io.opentelemetry.api.trace.SpanKind.CLIENT @Unroll abstract class AbstractAws2ClientCoreTest extends InstrumentationSpecification { - def isSqsAttributeInjectionEnabled() { + static boolean isSqsAttributeInjectionEnabled() { // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index a86c9bc8a81a..a3529957b636 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -16,7 +16,9 @@ import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsBaseClientBuilder import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest import software.amazon.awssdk.services.sqs.model.SendMessageRequest import spock.lang.Shared @@ -35,8 +37,25 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { @Shared int sqsPort + static Map dummyMessageAttributes(count) { + (0.. e.messageBody("e1").id("i1"), + // 8 attributes, injection always possible + e -> e.messageBody("e2").id("i2") + .messageAttributes(dummyMessageAttributes(8)), + // 10 attributes, injection with custom propagator never possible + e -> e.messageBody("e3").id("i3").messageAttributes(dummyMessageAttributes(10))) + .build() + + boolean isSqsAttributeInjectionEnabled() { + AbstractAws2ClientCoreTest.isSqsAttributeInjectionEnabled() + } + + boolean isXrayInjectionEnabled() { + true + } + void configureSdkClient(SqsBaseClientBuilder builder) { builder .overrideConfiguration(createOverrideConfigurationBuilder().build()) @@ -124,6 +162,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { name "Sqs.ReceiveMessage" kind CONSUMER childOf span(0) + hasNoLinks() // TODO: Link to receive operation? attributes { "aws.agent" "java-aws-sdk" "rpc.method" "ReceiveMessage" @@ -148,6 +187,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { name "Sqs.ReceiveMessage" kind CLIENT hasNoParent() + hasNoLinks() attributes { "aws.agent" "java-aws-sdk" "aws.requestId" "00000000-0000-0000-0000-000000000000" @@ -204,4 +244,133 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { resp.messages().size() == 1 assertSqsTraces() } + + def "batch sqs producer-consumer services: sync"() { + setup: + def builder = SqsClient.builder() + configureSdkClient(builder) + def client = builder.build() + + client.createQueue(createQueueRequest) + + when: + client.sendMessageBatch(sendMessageBatchRequest) + + def resp = client.receiveMessage(receiveMessageBatchRequest) + def totalAttrs = resp.messages().sum {it.messageAttributes().size() } + + then: + resp.messages().size() == 3 + + // +2: 3 messages, 2x traceparent, 1x not injected due to too many attrs + totalAttrs == 18 + (sqsAttributeInjectionEnabled ? 2 : 0) + + assertTraces(xrayInjectionEnabled ? 3 : 4) { + trace(0, 1) { + + span(0) { + name "Sqs.CreateQueue" + kind CLIENT + } + } + trace(1, xrayInjectionEnabled ? 4 : 3) { + span(0) { + name "Sqs.SendMessageBatch" + kind CLIENT // TODO: Probably this should be producer, but that would be a breaking change + hasNoParent() + attributes { + "aws.agent" "java-aws-sdk" + "aws.queue.url" "http://localhost:$sqsPort/000000000000/testSdkSqs" + "aws.requestId" "00000000-0000-0000-0000-000000000000" + "rpc.system" "aws-api" + "rpc.method" "SendMessageBatch" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + for (int i: 1..(xrayInjectionEnabled ? 3 : 2)) { + span(i) { + name "Sqs.ReceiveMessage" + kind CONSUMER + childOf span(0) + hasNoLinks() // TODO: Link to receive operation? + + attributes { + "aws.agent" "java-aws-sdk" + "rpc.method" "ReceiveMessage" + "rpc.system" "aws-api" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + } + } + /** + * This span represents HTTP "sending of receive message" operation. It's always single, while there can be multiple CONSUMER spans (one per consumed message). + * This one could be suppressed (by IF in TracingRequestHandler#beforeRequest but then HTTP instrumentation span would appear + */ + trace(2, 1) { + span(0) { + name "Sqs.ReceiveMessage" + kind CLIENT + hasNoParent() + attributes { + "aws.agent" "java-aws-sdk" + "aws.requestId" "00000000-0000-0000-0000-000000000000" + "rpc.method" "ReceiveMessage" + "aws.queue.url" "http://localhost:$sqsPort/000000000000/testSdkSqs" + "rpc.system" "aws-api" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + } + if (!xrayInjectionEnabled) { + trace(3, 1) { + span(0) { + name "Sqs.ReceiveMessage" + kind CONSUMER + + // TODO This is not nice at all, and can also happen if producer is not instrumented + hasNoParent() + hasNoLinks() // TODO: Link to receive operation? + + attributes { + "aws.agent" "java-aws-sdk" + "rpc.method" "ReceiveMessage" + "rpc.system" "aws-api" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + } + } + } + } } From d20da99e942e894aa82ca24306dcd2d603c0eedd Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:45:04 -0500 Subject: [PATCH 34/95] Log4j and Logback appenders opt-in to using GlobalOpenTelemetry (#8791) --- .../log4j-appender-2.17/library/README.md | 20 +++++ .../appender/v2_17/OpenTelemetryAppender.java | 43 ++++++++-- .../OpenTelemetryAppenderConfigTest.java | 57 -------------- ...ryAppenderConfigWithOpenTelemetryTest.java | 78 ------------------- ...se.java => OpenTelemetryAppenderTest.java} | 60 ++++++++++++-- .../resources/{log4j2-test.xml => log4j2.xml} | 0 .../logback-appender-1.0/library/README.md | 20 +++++ .../appender/v1_0/OpenTelemetryAppender.java | 41 +++++++++- .../logback/appender/v1_0/Slf4j2Test.java | 6 +- ...st.java => OpenTelemetryAppenderTest.java} | 12 +-- 10 files changed, 178 insertions(+), 159 deletions(-) delete mode 100644 instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java delete mode 100644 instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java rename instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/{OpenTelemetryAppenderConfigTestBase.java => OpenTelemetryAppenderTest.java} (77%) rename instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/{log4j2-test.xml => log4j2.xml} (100%) rename instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/{OpenTelemetryAppenderConfigTest.java => OpenTelemetryAppenderTest.java} (95%) diff --git a/instrumentation/log4j/log4j-appender-2.17/library/README.md b/instrumentation/log4j/log4j-appender-2.17/library/README.md index 4944d252da91..7af9214e0535 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/README.md +++ b/instrumentation/log4j/log4j-appender-2.17/library/README.md @@ -56,3 +56,23 @@ The following demonstrates how you might configure the appender in your `log4j.x In this example Log4j2 log events will be sent to both the console appender and the `OpenTelemetryAppender`. + +In order to function, `OpenTelemetryAppender` needs access to an `OpenTelemetry` instance. This must +be set programmatically during application startup as follows: + +```java +import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +public class Application { + + public static void main(String[] args) { + OpenTelemetrySdk openTelemetrySdk = // Configure OpenTelemetrySdk + + // Find OpenTelemetryAppender in log4j configuration and install openTelemetrySdk + OpenTelemetryAppender.install(openTelemetrySdk); + + // ... proceed with application + } +} +``` 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 cef3c084e8c4..ba670da34355 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 @@ -8,7 +8,6 @@ import static java.util.Collections.emptyList; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor; @@ -20,13 +19,16 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configuration; 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.PluginBuilderAttribute; @@ -44,7 +46,29 @@ public class OpenTelemetryAppender extends AbstractAppender { static final String PLUGIN_NAME = "OpenTelemetry"; private final LogEventMapper mapper; - @Nullable private OpenTelemetry openTelemetry; + private OpenTelemetry openTelemetry; + + /** + * Installs the {@code openTelemetry} instance on any {@link OpenTelemetryAppender}s identified in + * the {@link LoggerContext}. + */ + public static void install(OpenTelemetry openTelemetry) { + org.apache.logging.log4j.spi.LoggerContext loggerContextSpi = LogManager.getContext(false); + if (!(loggerContextSpi instanceof LoggerContext)) { + return; + } + LoggerContext loggerContext = (LoggerContext) loggerContextSpi; + Configuration config = loggerContext.getConfiguration(); + config + .getAppenders() + .values() + .forEach( + appender -> { + if (appender instanceof OpenTelemetryAppender) { + ((OpenTelemetryAppender) appender).setOpenTelemetry(openTelemetry); + } + }); + } @PluginBuilderFactory public static > B builder() { @@ -97,6 +121,7 @@ public B setCaptureContextDataAttributes(String captureContextDataAttributes) { return asBuilder(); } + /** Configures the {@link OpenTelemetry} used to append logs. */ @CanIgnoreReturnValue public B setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -105,6 +130,10 @@ public B setOpenTelemetry(OpenTelemetry openTelemetry) { @Override public OpenTelemetryAppender build() { + OpenTelemetry openTelemetry = this.openTelemetry; + if (openTelemetry == null) { + openTelemetry = OpenTelemetry.noop(); + } return new OpenTelemetryAppender( getName(), getLayout(), @@ -152,14 +181,14 @@ private static List splitAndFilterBlanksAndNulls(String value) { .collect(Collectors.toList()); } + /** + * Configures the {@link OpenTelemetry} used to append logs. This MUST be called for the appender + * to function. See {@link #install(OpenTelemetry)} for simple installation option. + */ public void setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } - private OpenTelemetry getOpenTelemetry() { - return openTelemetry == null ? GlobalOpenTelemetry.get() : openTelemetry; - } - @Override public void append(LogEvent event) { String instrumentationName = event.getLoggerName(); @@ -168,7 +197,7 @@ public void append(LogEvent event) { } LogRecordBuilder builder = - getOpenTelemetry() + this.openTelemetry .getLogsBridge() .loggerBuilder(instrumentationName) .build() diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java deleted file mode 100644 index f442c807fb41..000000000000 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.log4j.appender.v2_17; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import java.util.List; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.FormattedMessage; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class OpenTelemetryAppenderConfigTest extends OpenTelemetryAppenderConfigTestBase { - - @BeforeAll - static void setupAll() { - logRecordExporter = InMemoryLogRecordExporter.create(); - resource = Resource.getDefault(); - instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger"); - - SdkLoggerProvider loggerProvider = - SdkLoggerProvider.builder() - .setResource(resource) - .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) - .build(); - - GlobalOpenTelemetry.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); - } - - @Test - void initializeWithBuilder() { - OpenTelemetryAppender appender = - OpenTelemetryAppender.builder().setName("OpenTelemetryAppender").build(); - appender.start(); - - appender.append( - Log4jLogEvent.newBuilder() - .setMessage(new FormattedMessage("log message 1", (Object) null)) - .build()); - - List logDataList = logRecordExporter.getFinishedLogRecordItems(); - assertThat(logDataList) - .satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1")); - } -} diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java deleted file mode 100644 index 897bc7be916f..000000000000 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.log4j.appender.v2_17; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.FormattedMessage; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class OpenTelemetryAppenderConfigWithOpenTelemetryTest extends OpenTelemetryAppenderConfigTestBase { - @BeforeAll - static void setupAll() { - logRecordExporter = InMemoryLogRecordExporter.create(); - resource = Resource.getDefault(); - instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger"); - - SdkLoggerProvider loggerProvider = - SdkLoggerProvider.builder() - .setResource(resource) - .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) - .build(); - - GlobalOpenTelemetry.resetForTest(); - openTelemetry = OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); - setOpenTelemetry(openTelemetry); - } - - private static void setOpenTelemetry(OpenTelemetry openTelemetry) { - Configuration config = ((LoggerContext) LogManager.getContext(false)).getConfiguration(); - config.getAppenders().values().stream() - .filter(a -> a instanceof OpenTelemetryAppender) - .forEach(a -> ((OpenTelemetryAppender) a).setOpenTelemetry(openTelemetry)); - } - - @AfterAll - static void cleanupAll() { - // This is to make sure that other test classes don't have issues with the logger provider set - setOpenTelemetry(null); - } - - @Test - void initializeWithBuilder() { - OpenTelemetryAppender appender = - OpenTelemetryAppender.builder() - .setName("OpenTelemetryAppender") - .setOpenTelemetry(openTelemetry) - .build(); - appender.start(); - - appender.append( - Log4jLogEvent.newBuilder() - .setMessage(new FormattedMessage("log message 1", (Object) null)) - .build()); - - List logDataList = logRecordExporter.getFinishedLogRecordItems(); - assertThat(logDataList) - .satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1")); - } -} diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java similarity index 77% rename from instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java rename to instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java index 535937e5fc3f..87025bc82c33 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java @@ -17,8 +17,11 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -30,19 +33,39 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.StringMapMessage; import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -abstract class OpenTelemetryAppenderConfigTestBase { +class OpenTelemetryAppenderTest { - static final Logger logger = LogManager.getLogger("TestLogger"); + private static final Logger logger = LogManager.getLogger("TestLogger"); - static InMemoryLogRecordExporter logRecordExporter; - static Resource resource; - static InstrumentationScopeInfo instrumentationScopeInfo; - static OpenTelemetry openTelemetry; + private static InMemoryLogRecordExporter logRecordExporter; + private static Resource resource; + private static InstrumentationScopeInfo instrumentationScopeInfo; + private static OpenTelemetry openTelemetry; + + @BeforeAll + static void setupAll() { + logRecordExporter = InMemoryLogRecordExporter.create(); + resource = Resource.getDefault(); + instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger"); + + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + .setResource(resource) + .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) + .build(); + + openTelemetry = OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); + OpenTelemetryAppender.install(openTelemetry); + } @BeforeEach void setup() { @@ -50,6 +73,31 @@ void setup() { ThreadContext.clearAll(); } + @AfterAll + static void cleanupAll() { + // This is to make sure that other test classes don't have issues with the logger provider set + OpenTelemetryAppender.install(null); + } + + @Test + void initializeWithBuilder() { + OpenTelemetryAppender appender = + OpenTelemetryAppender.builder() + .setName("OpenTelemetryAppender") + .setOpenTelemetry(openTelemetry) + .build(); + appender.start(); + + appender.append( + Log4jLogEvent.newBuilder() + .setMessage(new FormattedMessage("log message 1", (Object) null)) + .build()); + + List logDataList = logRecordExporter.getFinishedLogRecordItems(); + assertThat(logDataList) + .satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1")); + } + @Test void logNoSpan() { logger.info("log message 1"); diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2-test.xml b/instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2.xml similarity index 100% rename from instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2-test.xml rename to instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2.xml diff --git a/instrumentation/logback/logback-appender-1.0/library/README.md b/instrumentation/logback/logback-appender-1.0/library/README.md index 171d02b63efb..f997d94d7e28 100644 --- a/instrumentation/logback/logback-appender-1.0/library/README.md +++ b/instrumentation/logback/logback-appender-1.0/library/README.md @@ -60,3 +60,23 @@ The following demonstrates how you might configure the appender in your `logback In this example Logback log events will be sent to both the console appender and the `OpenTelemetryAppender`. + +In order to function, `OpenTelemetryAppender` needs access to an `OpenTelemetry` instance. This must +be set programmatically during application startup as follows: + +```java +import io.opentelemetry.instrumentation.logback.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +public class Application { + + public static void main(String[] args) { + OpenTelemetrySdk openTelemetrySdk = // Configure OpenTelemetrySdk + + // Find OpenTelemetryAppender in logback configuration and install openTelemetrySdk + OpenTelemetryAppender.install(openTelemetrySdk); + + // ... proceed with application + } +} +``` diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java index b161d54d6009..b39e5b6d7201 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java @@ -7,13 +7,16 @@ import static java.util.Collections.emptyList; +import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.UnsynchronizedAppenderBase; -import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class OpenTelemetryAppender extends UnsynchronizedAppenderBase { @@ -24,10 +27,33 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase captureMdcAttributes = emptyList(); + private OpenTelemetry openTelemetry; private LoggingEventMapper mapper; public OpenTelemetryAppender() {} + /** + * Installs the {@code openTelemetry} instance on any {@link OpenTelemetryAppender}s identified in + * the {@link LoggerContext}. + */ + public static void install(OpenTelemetry openTelemetry) { + ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory(); + if (!(loggerFactorySpi instanceof LoggerContext)) { + return; + } + LoggerContext loggerContext = (LoggerContext) loggerFactorySpi; + for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) { + logger + .iteratorForAppenders() + .forEachRemaining( + appender -> { + if (appender instanceof OpenTelemetryAppender) { + ((OpenTelemetryAppender) appender).setOpenTelemetry(openTelemetry); + } + }); + } + } + @Override public void start() { mapper = @@ -37,12 +63,15 @@ public void start() { captureCodeAttributes, captureMarkerAttribute, captureKeyValuePairAttributes); + if (openTelemetry == null) { + openTelemetry = OpenTelemetry.noop(); + } super.start(); } @Override protected void append(ILoggingEvent event) { - mapper.emit(GlobalOpenTelemetry.get().getLogsBridge(), event); + mapper.emit(openTelemetry.getLogsBridge(), event); } /** @@ -93,6 +122,14 @@ public void setCaptureMdcAttributes(String attributes) { } } + /** + * Configures the {@link OpenTelemetry} used to append logs. This MUST be called for the appender + * to function. See {@link #install(OpenTelemetry)} for simple installation option. + */ + public void setOpenTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + // copied from SDK's DefaultConfigProperties private static List filterBlanksAndNulls(String[] values) { return Arrays.stream(values) diff --git a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java index 80734e80fc02..706aae5e0c49 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java @@ -7,7 +7,6 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -43,9 +42,10 @@ static void setupAll() { .setResource(resource) .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) .build(); + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); - GlobalOpenTelemetry.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); + OpenTelemetryAppender.install(openTelemetrySdk); } @BeforeEach diff --git a/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderConfigTest.java b/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderTest.java similarity index 95% rename from instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderConfigTest.java rename to instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderTest.java index 2fc5271141a8..defe44a15e0c 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderConfigTest.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderTest.java @@ -7,7 +7,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; @@ -35,7 +34,7 @@ import org.slf4j.Marker; import org.slf4j.MarkerFactory; -class OpenTelemetryAppenderConfigTest { +class OpenTelemetryAppenderTest { private static final Logger logger = LoggerFactory.getLogger("TestLogger"); @@ -54,9 +53,10 @@ static void setupAll() { .setResource(resource) .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) .build(); + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); - GlobalOpenTelemetry.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); + OpenTelemetryAppender.install(openTelemetrySdk); } @BeforeEach @@ -130,12 +130,12 @@ void logWithExtras() { .contains("logWithExtras"); String file = logData.getAttributes().get(SemanticAttributes.CODE_FILEPATH); - assertThat(file).isEqualTo("OpenTelemetryAppenderConfigTest.java"); + assertThat(file).isEqualTo("OpenTelemetryAppenderTest.java"); String codeClass = logData.getAttributes().get(SemanticAttributes.CODE_NAMESPACE); assertThat(codeClass) .isEqualTo( - "io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppenderConfigTest"); + "io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppenderTest"); String method = logData.getAttributes().get(SemanticAttributes.CODE_FUNCTION); assertThat(method).isEqualTo("logWithExtras"); From f4e25242986d0f8f619be11d092be310a2e9fbda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:23:35 -0700 Subject: [PATCH 35/95] Bump com.google.guava:guava-bom from 32.0.1-jre to 32.1.0-jre (#8835) --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 4089230e94d8..0b0065587d2b 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -31,7 +31,7 @@ val groovyVersion = "4.0.12" val DEPENDENCY_BOMS = listOf( "com.fasterxml.jackson:jackson-bom:2.15.2", - "com.google.guava:guava-bom:32.0.1-jre", + "com.google.guava:guava-bom:32.1.0-jre", "org.apache.groovy:groovy-bom:${groovyVersion}", "io.opentelemetry:opentelemetry-bom:${otelSdkVersion}", "io.opentelemetry:opentelemetry-bom-alpha:${otelSdkAlphaVersion}", From 87d2aa881b26c436617018526d7a1103dbbe18f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:25:46 -0700 Subject: [PATCH 36/95] Bump groovyVersion from 4.0.12 to 4.0.13 (#8834) --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 0b0065587d2b..a673a1e7fa3f 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -16,7 +16,7 @@ val otelSdkVersion = "1.27.0" val otelSdkAlphaVersion = otelSdkVersion.replaceFirst("(-SNAPSHOT)?$".toRegex(), "-alpha$1") // Need both BOM and groovy jars -val groovyVersion = "4.0.12" +val groovyVersion = "4.0.13" // We don't force libraries we instrument to new versions since we compile and test against specific // old baseline versions but we do try to force those libraries' transitive dependencies to new From c9ffa55867e5efa46905f62f9470fc8053e9ae1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 08:45:45 +0300 Subject: [PATCH 37/95] Bump com.google.guava:guava from 32.0.1-jre to 32.1.0-jre (#8836) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- conventions/build.gradle.kts | 2 +- gradle-plugins/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index 17dc508103e7..aa650398849b 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { // When updating, update above in plugins too implementation("com.diffplug.spotless:spotless-plugin-gradle:6.19.0") - implementation("com.google.guava:guava:32.0.1-jre") + implementation("com.google.guava:guava:32.1.0-jre") implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") diff --git a/gradle-plugins/build.gradle.kts b/gradle-plugins/build.gradle.kts index d9e0c3d8cfa0..360bfb34ef92 100644 --- a/gradle-plugins/build.gradle.kts +++ b/gradle-plugins/build.gradle.kts @@ -28,7 +28,7 @@ val byteBuddyVersion = "1.14.5" val aetherVersion = "1.1.0" dependencies { - implementation("com.google.guava:guava:32.0.1-jre") + implementation("com.google.guava:guava:32.1.0-jre") // we need to use byte buddy variant that does not shade asm implementation("net.bytebuddy:byte-buddy-gradle-plugin:${byteBuddyVersion}") { exclude(group = "net.bytebuddy", module = "byte-buddy") From 93fc34ebfbd9cfbca765ff7094e2e00fa73bbad2 Mon Sep 17 00:00:00 2001 From: Anthony Quinones Date: Fri, 30 Jun 2023 10:14:36 -0500 Subject: [PATCH 38/95] converted couchbase-3.2 test from groovy to java (#8837) --- .../test/groovy/CouchbaseClient32Test.groovy | 80 ----------------- .../couchbase/v3_2/CouchbaseClient32Test.java | 85 +++++++++++++++++++ 2 files changed, 85 insertions(+), 80 deletions(-) delete mode 100644 instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy create mode 100644 instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java diff --git a/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy b/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy deleted file mode 100644 index 541cb8a2caf6..000000000000 --- a/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.couchbase.client.core.error.DocumentNotFoundException -import com.couchbase.client.java.Cluster -import com.couchbase.client.java.Collection -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.testcontainers.containers.output.Slf4jLogConsumer -import org.testcontainers.couchbase.BucketDefinition -import org.testcontainers.couchbase.CouchbaseContainer -import org.testcontainers.couchbase.CouchbaseService -import spock.lang.Shared - -import java.time.Duration - -import static io.opentelemetry.api.trace.StatusCode.ERROR - -// Couchbase instrumentation is owned upstream so we don't assert on the contents of the spans, only -// that the instrumentation is properly registered by the agent, meaning some spans were generated. -class CouchbaseClient32Test extends AgentInstrumentationSpecification { - private static final Logger logger = LoggerFactory.getLogger("couchbase-container") - - @Shared - CouchbaseContainer couchbase - @Shared - Cluster cluster - @Shared - Collection collection - - def setupSpec() { - couchbase = new CouchbaseContainer() - .withExposedPorts(8091) - .withEnabledServices(CouchbaseService.KV) - .withBucket(new BucketDefinition("test")) - .withLogConsumer(new Slf4jLogConsumer(logger)) - .withStartupTimeout(Duration.ofSeconds(120)) - couchbase.start() - - cluster = Cluster.connect(couchbase.connectionString, couchbase.username, couchbase.password) - def bucket = cluster.bucket("test") - collection = bucket.defaultCollection() - bucket.waitUntilReady(Duration.ofSeconds(30)) - } - - def cleanupSpec() { - couchbase.stop() - } - - def "emits spans"() { - when: - try { - collection.get("id") - } catch (DocumentNotFoundException e) { - // Expected - } - - then: - assertTracesWithoutScopeVersionVerification(1) { - trace(0, 2) { - span(0) { - name(~/.*get/) - if (Boolean.getBoolean("testLatestDeps")) { - // this is the correct behavior - status ERROR - } - } - span(1) { - name(~/.*dispatch_to_server/) - } - } - } - - cleanup: - cluster.disconnect() - } -} diff --git a/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java b/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java new file mode 100644 index 000000000000..dc859eb4f092 --- /dev/null +++ b/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.couchbase.v3_2; + +import com.couchbase.client.core.error.DocumentNotFoundException; +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.Collection; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.time.Duration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.couchbase.BucketDefinition; +import org.testcontainers.couchbase.CouchbaseContainer; +import org.testcontainers.couchbase.CouchbaseService; + +// Couchbase instrumentation is owned upstream so we don't assert on the contents of the spans, only +// that the instrumentation is properly registered by the agent, meaning some spans were generated. +class CouchbaseClient32Test { + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final Logger logger = LoggerFactory.getLogger("couchbase-container"); + + static CouchbaseContainer couchbase; + static Cluster cluster; + static Collection collection; + + @BeforeAll + static void setup() { + couchbase = + new CouchbaseContainer("couchbase/server:6.5.1") + .withExposedPorts(8091) + .withEnabledServices(CouchbaseService.KV) + .withBucket(new BucketDefinition("test")) + .withLogConsumer(new Slf4jLogConsumer(logger)) + .withStartupTimeout(Duration.ofMinutes(2)); + couchbase.start(); + + cluster = + Cluster.connect( + couchbase.getConnectionString(), couchbase.getUsername(), couchbase.getPassword()); + Bucket bucket = cluster.bucket("test"); + collection = bucket.defaultCollection(); + bucket.waitUntilReady(Duration.ofSeconds(30)); + } + + @AfterAll + static void cleanup() { + cluster.disconnect(); + couchbase.stop(); + } + + @Test + void testEmitsSpans() { + try { + collection.get("id"); + } catch (DocumentNotFoundException e) { + // Expected + } + + testing.waitAndAssertTracesWithoutScopeVersionVerification( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> { + span.hasName("get"); + if (Boolean.getBoolean("testLatestDeps")) { + span.hasStatus(StatusData.error()); + } + }, + span -> span.hasName("dispatch_to_server"))); + } +} From f2799056f3f6be7d0362dc1782e39ec7c88c988a Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 30 Jun 2023 18:17:33 +0300 Subject: [PATCH 39/95] Correct class name in javadoc code snippet (#8840) --- .../instrumentation/spring/web/v3_1/SpringWebTelemetry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java b/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java index 36209d7736cc..70cfc713df84 100644 --- a/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java +++ b/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java @@ -39,7 +39,7 @@ public static SpringWebTelemetryBuilder builder(OpenTelemetry openTelemetry) { * RestTemplate#getInterceptors()}. For example: * *

{@code
-   * restTemplate.getInterceptors().add(SpringWebTracing.create(openTelemetry).newInterceptor());
+   * restTemplate.getInterceptors().add(SpringWebTelemetry.create(openTelemetry).newInterceptor());
    * }
*/ public ClientHttpRequestInterceptor newInterceptor() { From d9aac1679aa5b5454a8f9e8aa878ac9e949eacce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 30 Jun 2023 17:19:38 +0200 Subject: [PATCH 40/95] aws-sdk-2.2: SNS.Publish support with experimental messaging propagator flag (#8830) --- instrumentation/aws-sdk/README.md | 8 +- .../aws-sdk-2.2/javaagent/build.gradle.kts | 18 +++++ .../awssdk/v2_2/SnsAdviceBridge.java | 15 ++++ .../awssdk/v2_2/SqsAdviceBridge.java | 0 .../awssdk/v2_2/SnsInstrumentationModule.java | 38 ++++++++++ .../library-autoconfigure/build.gradle.kts | 1 + .../aws-sdk/aws-sdk-2.2/library/README.md | 18 ++++- .../aws-sdk-2.2/library/build.gradle.kts | 1 + .../awssdk/v2_2/SnsAccess.java | 23 ++++++ .../instrumentation/awssdk/v2_2/SnsImpl.java | 75 +++++++++++++++++++ .../v2_2/TracingExecutionInterceptor.java | 12 ++- .../aws-sdk-2.2/testing/build.gradle.kts | 1 + .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 22 ++++++ 13 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java rename instrumentation/aws-sdk/aws-sdk-2.2/{library => javaagent}/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java (100%) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java diff --git a/instrumentation/aws-sdk/README.md b/instrumentation/aws-sdk/README.md index afc2b69874a7..d3f4893f33b5 100644 --- a/instrumentation/aws-sdk/README.md +++ b/instrumentation/aws-sdk/README.md @@ -5,7 +5,7 @@ For more information, see the respective public setters in the `AwsSdkTelemetryB * [SDK v1](./aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTelemetryBuilder.java) * [SDK v2](./aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetryBuilder.java) -| System property | Type | Default | Description | -|---|---|---|------------------------------------------------------------------------------------------------------------------------------------------------| -| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | -| `otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging` | Boolean | `false` | Enable propagation via message attributes using configured propagator (in addition to X-Ray). At the moment, Supports only SQS and the v2 SDK. | +| System property | Type | Default | Description | +|---|---|---|---------------------------------------------------------------------------------------------------------------------------------------| +| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | +| `otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging` | Boolean | `false` | v2 only, inject into SNS/SQS attributes with configured propagator: See [v2 README](aws-sdk-2.2/library/README.md#trace-propagation). | diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index f5faf7d14c7c..e3e6e2d69713 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -11,6 +11,7 @@ muzzle { // client, which is not target of instrumentation anyways. extraDependency("software.amazon.awssdk:protocol-core") excludeInstrumentationName("aws-sdk-2.2-sqs") + excludeInstrumentationName("aws-sdk-2.2-sns") // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") @@ -40,6 +41,22 @@ muzzle { // client, which is not target of instrumentation anyways. extraDependency("software.amazon.awssdk:protocol-core") + excludeInstrumentationName("aws-sdk-2.2-sns") + + // several software.amazon.awssdk artifacts are missing for this version + skip("2.17.200") + } + + pass { + group.set("software.amazon.awssdk") + module.set("sns") + versions.set("[2.2.0,)") + // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP + // client, which is not target of instrumentation anyways. + extraDependency("software.amazon.awssdk:protocol-core") + + excludeInstrumentationName("aws-sdk-2.2-sqs") + // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") } @@ -63,6 +80,7 @@ dependencies { testLibrary("software.amazon.awssdk:rds:2.2.0") testLibrary("software.amazon.awssdk:s3:2.2.0") testLibrary("software.amazon.awssdk:sqs:2.2.0") + testLibrary("software.amazon.awssdk:sns:2.2.0") } tasks { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java new file mode 100644 index 000000000000..9077412ec258 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java @@ -0,0 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +public final class SnsAdviceBridge { + private SnsAdviceBridge() {} + + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SnsImpl.class.getName() + " referencing for muzzle, should never be actually called"); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java similarity index 100% rename from instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java rename to instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java new file mode 100644 index 000000000000..82158fa5318a --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; + +import static net.bytebuddy.matcher.ElementMatchers.none; + +import com.google.auto.service.AutoService; +import io.opentelemetry.instrumentation.awssdk.v2_2.SnsAdviceBridge; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumentationModule.class) +public class SnsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { + + public SnsInstrumentationModule() { + super("aws-sdk-2.2-sns"); + } + + @Override + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SnsInstrumentationModule.class.getName() + "$RegisterAdvice"); + } + + @SuppressWarnings("unused") + public static class RegisterAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit() { + // (indirectly) using SnsImpl class here to make sure it is available from SnsAccess + // (injected into app classloader) and checked by Muzzle + SnsAdviceBridge.referenceForMuzzleOnly(); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts index 270ad6e23906..3c4d6758fb47 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { testLibrary("software.amazon.awssdk:rds:2.2.0") testLibrary("software.amazon.awssdk:s3:2.2.0") testLibrary("software.amazon.awssdk:sqs:2.2.0") + testLibrary("software.amazon.awssdk:sns:2.2.0") } tasks { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md b/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md index 7e473fee406b..96fb50bf649e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md @@ -18,9 +18,23 @@ DynamoDbClient client = DynamoDbClient.builder() ## Trace propagation -The AWS SDK instrumentation currently only supports injecting the trace header into the request +The AWS SDK instrumentation always injects the trace header into the request using the [AWS Trace Header](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader) format. This format is the only format recognized by AWS managed services, and populating will allow -propagating the trace through them. If this does not fulfill your use case, perhaps because you are +propagating the trace through them. + +Additionally, you can enable an experimental option to use the configured propagator to inject into +message attributes (see [parent README](../../README.md)). This currently supports the following AWS APIs: + +* SQS.SendMessage +* SQS.SendMessageBatch +* SNS.Publish + (SNS.PublishBatch is not supported at the moment because it is not available in the minimum SDK + version targeted by the instrumentation) + +Note that injection will only happen if, after injection, a maximum of 10 attributes is used to not +run over API limitations set by AWS. + +If this does not fulfill your use case, perhaps because you are using the same SDK with a different non-AWS managed service, let us know so we can provide configuration for this behavior. diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 94e6c648e138..9f47f299644f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { library("software.amazon.awssdk:aws-core:2.2.0") library("software.amazon.awssdk:sqs:2.2.0") + library("software.amazon.awssdk:sns:2.2.0") library("software.amazon.awssdk:aws-json-protocol:2.2.0") compileOnly(project(":muzzle")) // For @NoMuzzle diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java new file mode 100644 index 000000000000..7c9cd1bc596d --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; +import software.amazon.awssdk.core.SdkRequest; + +final class SnsAccess { + private SnsAccess() {} + + private static final boolean enabled = PluginImplUtil.isImplPresent("SnsImpl"); + + @NoMuzzle + public static SdkRequest modifyRequest( + SdkRequest request, Context otelContext, TextMapPropagator messagingPropagator) { + return enabled ? SnsImpl.modifyRequest(request, otelContext, messagingPropagator) : null; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java new file mode 100644 index 000000000000..464b50922c5c --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import java.util.HashMap; +import java.util.Map; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.services.sns.SnsClient; +import software.amazon.awssdk.services.sns.model.MessageAttributeValue; +import software.amazon.awssdk.services.sns.model.PublishRequest; + +// this class is only used from SnsAccess from method with @NoMuzzle annotation +class SnsImpl { + static { + // Force loading of SnsClient; this ensures that an exception is thrown at this point when the + // SNS library is not present, which will cause SnsAccess to have enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = SnsClient.class.getName(); + } + + private SnsImpl() {} + + static SdkRequest modifyRequest( + SdkRequest request, Context otelContext, TextMapPropagator messagingPropagator) { + if (messagingPropagator == null) { + return null; + } else if (request instanceof PublishRequest) { + return injectIntoPublishRequest((PublishRequest) request, otelContext, messagingPropagator); + } else { + // NB: We do not support PublishBatchRequest which was only introduced in 2.17.84. + // To add support, some targeted use of @NoMuzzle + checks that the needed class + // is available should work. See + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8830#discussion_r1247570985 + return null; + } + } + + private static SdkRequest injectIntoPublishRequest( + PublishRequest request, Context otelContext, TextMapPropagator messagingPropagator) { + // Note: Code is 1:1 copy & paste from SQS, but due to different types (packages) cannot be + // reused. + Map messageAttributes = + new HashMap<>(request.messageAttributes()); + if (!injectIntoMessageAttributes(messageAttributes, otelContext, messagingPropagator)) { + return request; + } + return request.toBuilder().messageAttributes(messageAttributes).build(); + } + + private static boolean injectIntoMessageAttributes( + Map messageAttributes, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + // Note: Code is 1:1 copy & paste from SQS, but due to different types (packages) cannot be + // reused. + messagingPropagator.inject( + otelContext, + messageAttributes, + (carrier, k, v) -> { + carrier.put(k, MessageAttributeValue.builder().stringValue(v).dataType("String").build()); + }); + + // Return whether the injection resulted in an attribute count that is still supported. + // See https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html + // While non-raw delivery would support an arbitrary number, that is something configured in + // the subscription, and adding more attributes might result in odd behavior (e.g. we might + // push out other attributes) + return messageAttributes.size() <= 10; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index ed2c9bae8b5e..d60a29bf812a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -121,13 +121,17 @@ public SdkRequest modifyRequest( throw throwable; } - SdkRequest sqsModifiedRequest = + SdkRequest modifiedRequest = SqsAccess.modifyRequest(request, otelContext, useXrayPropagator, messagingPropagator); - if (sqsModifiedRequest != null) { - return sqsModifiedRequest; + if (modifiedRequest != null) { + return modifiedRequest; + } + modifiedRequest = SnsAccess.modifyRequest(request, otelContext, messagingPropagator); + if (modifiedRequest != null) { + return modifiedRequest; } - // Insert other special handling here, following the same pattern as SQS. + // Insert other special handling here, following the same pattern as SQS and SNS. return request; } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 4e85462c32ba..4a7738056952 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { compileOnly("software.amazon.awssdk:rds:2.2.0") compileOnly("software.amazon.awssdk:s3:2.2.0") compileOnly("software.amazon.awssdk:sqs:2.2.0") + compileOnly("software.amazon.awssdk:sns:2.2.0") // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.12:1.0.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index 6acb8980c0f8..a193d167cb0c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -28,6 +28,7 @@ import software.amazon.awssdk.services.s3.S3AsyncClient import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.CreateBucketRequest import software.amazon.awssdk.services.s3.model.GetObjectRequest +import software.amazon.awssdk.services.sns.SnsAsyncClient import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest @@ -200,6 +201,17 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { request.request().headers().get("X-Amzn-Trace-Id") != null request.request().headers().get("traceparent") == null + if (service == "Sns" && operation == "Publish") { + def content = request.request().content().toStringUtf8() + def containsId = content.contains("${traces[0][0].traceId}-${traces[0][0].spanId}") + def containsTp = content.contains("=traceparent") + if (isSqsAttributeInjectionEnabled()) { + assert containsId && containsTp + } else { + assert !containsId && !containsTp + } + } + where: service | operation | method | path | requestId | builder | call | body "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" @@ -234,6 +246,16 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 """ + "Sns" | "Publish" | "POST" | "" | "f187a3c1-376f-11df-8963-01868b7c937a" | SnsAsyncClient.builder() | { SnsAsyncClient c -> c.publish(r -> r.message("hello")) } | """ + + + 94f20ce6-13c5-43a0-9a9e-ca52d816e90b + + + f187a3c1-376f-11df-8963-01868b7c937a + + + """ } // TODO(anuraaga): Without AOP instrumentation of the HTTP client, we cannot model retries as From a10d8aca7155aaadd742dff7d9029e79267fc60b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 22:04:04 +0000 Subject: [PATCH 41/95] Bump com.google.guava:guava from 32.1.0-jre to 32.1.1-jre (#8845) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- conventions/build.gradle.kts | 2 +- gradle-plugins/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index aa650398849b..720fe41c812f 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { // When updating, update above in plugins too implementation("com.diffplug.spotless:spotless-plugin-gradle:6.19.0") - implementation("com.google.guava:guava:32.1.0-jre") + implementation("com.google.guava:guava:32.1.1-jre") implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") diff --git a/gradle-plugins/build.gradle.kts b/gradle-plugins/build.gradle.kts index 360bfb34ef92..85f713c6e3e3 100644 --- a/gradle-plugins/build.gradle.kts +++ b/gradle-plugins/build.gradle.kts @@ -28,7 +28,7 @@ val byteBuddyVersion = "1.14.5" val aetherVersion = "1.1.0" dependencies { - implementation("com.google.guava:guava:32.1.0-jre") + implementation("com.google.guava:guava:32.1.1-jre") // we need to use byte buddy variant that does not shade asm implementation("net.bytebuddy:byte-buddy-gradle-plugin:${byteBuddyVersion}") { exclude(group = "net.bytebuddy", module = "byte-buddy") From 409882fd781dda588f1028f7157178509c61b0e5 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Fri, 30 Jun 2023 17:15:57 -0700 Subject: [PATCH 42/95] update quarkus version (#8846) --- smoke-tests/images/quarkus/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smoke-tests/images/quarkus/build.gradle.kts b/smoke-tests/images/quarkus/build.gradle.kts index f236c3b2118b..b27f4ed5e7a7 100644 --- a/smoke-tests/images/quarkus/build.gradle.kts +++ b/smoke-tests/images/quarkus/build.gradle.kts @@ -12,11 +12,11 @@ plugins { id("otel.java-conventions") id("com.google.cloud.tools.jib") - id("io.quarkus") version "3.1.2.Final" + id("io.quarkus") version "3.2.0.Final" } dependencies { - implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.1.3.Final")) + implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.2.0.CR1")) implementation("io.quarkus:quarkus-resteasy") } From cd13fd40189d7297e953e68a8d2a4be1c68f56d9 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 30 Jun 2023 18:13:30 -0700 Subject: [PATCH 43/95] Remove unused workflow (#8847) --- .github/repository-settings.md | 5 - .../comment-driven-pr-automation.yml | 139 ------------------ 2 files changed, 144 deletions(-) delete mode 100644 .github/workflows/comment-driven-pr-automation.yml diff --git a/.github/repository-settings.md b/.github/repository-settings.md index a427bf26708c..35b75d5bc3c6 100644 --- a/.github/repository-settings.md +++ b/.github/repository-settings.md @@ -10,11 +10,6 @@ settings](https://github.com/open-telemetry/community/blob/main/docs/how-to-conf * Allow auto-merge -## Collaborators and teams - -* [@opentelemetrybot](https://github.com/opentelemetrybot) has write permission in order to - enable [comment-driven PR automation](workflows/comment-driven-pr-automation.yml). - ## Actions > General * Fork pull request workflows from outside collaborators: diff --git a/.github/workflows/comment-driven-pr-automation.yml b/.github/workflows/comment-driven-pr-automation.yml deleted file mode 100644 index 1a9b4ed01d04..000000000000 --- a/.github/workflows/comment-driven-pr-automation.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: Comment driven automations -on: - issue_comment: - types: [ created ] - -jobs: - comment-driven-automation: - if: | - github.event.issue.pull_request && - startsWith(github.event.comment.body, '@opentelemetrybot ') - - runs-on: ubuntu-latest - steps: - - name: Get command - env: - BODY: ${{ github.event.comment.body }} - run: | - # intentionally only looking at the first line of the body - command=$(echo "$BODY" | head -1 | sed "s/^@opentelemetrybot //") - echo "COMMAND=$command" >> $GITHUB_ENV - - - uses: actions/checkout@v3 - with: - # history is needed for the update command to run "git merge" - fetch-depth: ${{ env.COMMAND == 'update' && '0' || '1' }} - # this is the personal access token used for "git push" below - # which is needed in order to trigger workflows - token: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} - - - name: Check out PR branch - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' || - env.COMMAND == 'update' - env: - NUMBER: ${{ github.event.issue.number }} - GH_TOKEN: ${{ github.token }} - run: | - gh pr checkout $NUMBER - - - name: This could take me a few minutes... - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' - env: - GH_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} - run: | - if [[ "$COMMAND" == "spotless" ]]; then - command="./gradlew spotlessApply" - elif [[ "$COMMAND" == "license" ]]; then - command="./gradlew generateLicenseReport" - elif [[ "$COMMAND" == "apidiff" ]]; then - command="./gradlew jApiCmp" - fi - gh pr comment $NUMBER --body "Running \`$command\`, this could take a few minutes..." - - - name: Set up Gradle cache - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' - uses: gradle/gradle-build-action@v2 - with: - cache-read-only: true - - - name: Use CLA approved github bot - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' || - env.COMMAND == 'update' - run: .github/scripts/use-cla-approved-github-bot.sh - - - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 17.0.6 - - - name: Run command - env: - NUMBER: ${{ github.event.issue.number }} - GH_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} - run: | - available_commands="Available commands: - * \`@opentelemetrybot spotless\` - runs \`./gradlew spotlessApply\` - * \`@opentelemetrybot license\` - runs \`./gradlew generateLicenseReport\` - * \`@opentelemetrybot apidiff\` - runs \`./gradlew jApiCmp\` - * \`@opentelemetrybot update\` - updates branch with merge commit - * \`@opentelemetrybot rerun\` - re-runs failed checks (NOT IMPLEMENTED YET) - * \`@opentelemetrybot help\` - displays available commands - " - # TODO add thumbs up on triggering comment - if [[ "$COMMAND" == "spotless" ]]; then - ./gradlew spotlessApply - if git diff --quiet; then - gh pr comment $NUMBER --body "Already up-to-date" - exit 0 # success - fi - git commit -a -m "./gradlew spotlessApply" - git push - elif [[ "$COMMAND" == "license" ]]; then - ./gradlew generateLicenseReport - git add licenses - # there's always going to one line difference due to the timestamp included in the report - if [[ $(git diff --cached --shortstat licenses) == " 1 file changed, 1 insertion(+), 1 deletion(-)" ]] - then - gh pr comment $NUMBER --body "Already up-to-date" - exit 0 # success - fi - git commit -m "./gradlew generateLicenseReport" - git push - elif [[ "$COMMAND" == "apidiff" ]]; then - ./gradlew jApiCmp - git add docs/apidiffs - if git diff --cached --quiet; then - gh pr comment $NUMBER --body "Already up-to-date" - exit 0 # success - fi - git commit -m "./gradlew jApiCmp" - git push - elif [[ "$COMMAND" == "update" ]]; then - # TODO check for up-to-date - git merge --no-edit origin/main - git push - elif [[ "$COMMAND" == "rerun" ]]; then - echo TODO - # gh run rerun --failed - elif [[ "$COMMAND" == "help" ]]; then - gh pr comment $NUMBER --body "$available_commands" - else - body="Unknown command: \`$COMMAND\` - - $available_commands - " - gh pr comment $NUMBER --body "$body" - fi From f315d80ed4e45eb7ccc4b0cc6a13137e86e25d07 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Sat, 1 Jul 2023 18:00:41 +0300 Subject: [PATCH 44/95] Run update-gradle-wrapper with jdk 17 (#8849) --- .github/workflows/update-gradle-wrappers-daily.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/update-gradle-wrappers-daily.yml b/.github/workflows/update-gradle-wrappers-daily.yml index e0c8e3bd398b..fcac7092822e 100644 --- a/.github/workflows/update-gradle-wrappers-daily.yml +++ b/.github/workflows/update-gradle-wrappers-daily.yml @@ -13,6 +13,12 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Set up JDK for running Gradle + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17.0.6 + - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 From 7b729618a8c51f3a337ca5d2d7682b677bc0c096 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Jul 2023 14:39:54 +0300 Subject: [PATCH 45/95] Update Gradle Wrapper to 8.2 (#8852) Signed-off-by: gradle-update-robot Co-authored-by: gradle-update-robot --- .../gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63375 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +++- benchmark-overhead/gradlew | 12 ++++++++---- .../distro/gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63375 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +++- examples/distro/gradlew | 12 ++++++++---- .../gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63375 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +++- examples/extension/gradlew | 12 ++++++++---- .../gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63375 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +++- gradle-plugins/gradlew | 12 ++++++++---- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 4 +++- gradlew | 12 ++++++++---- 15 files changed, 55 insertions(+), 25 deletions(-) diff --git a/benchmark-overhead/gradle/wrapper/gradle-wrapper.jar b/benchmark-overhead/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 21856 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 delta 20086 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/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/examples/distro/gradle/wrapper/gradle-wrapper.jar b/examples/distro/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 21856 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 delta 20086 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/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/examples/extension/gradle/wrapper/gradle-wrapper.jar b/examples/extension/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 21856 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 delta 20086 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/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradle-plugins/gradle/wrapper/gradle-wrapper.jar b/gradle-plugins/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 21856 zcmY(pb8Ie5)b?B3wr$(C-Cf%@?qb)rZDZHAd)IcmYh%~=p6{IZBgnaoP&k4a{& zS#w>#$^-C(Tku8=J49k2nL;->2uKtZ2nc(Mi5&qz#l+oO!_~yo!qv^hUfkNk%+A7< z$;{rw%`HtsUmIT&<2xL}5=NX^y$O;|$~RbH3)fdvzNjTrt*)GwOQcNFAi_C2*OLu> zE;mTv?XZ9ZFpwcsiA*b>@qUxw+Brv2W)p`@WtTU-`*C%<)&6#QYxD!+47q1MpD>LZ zdlU_bs==zy%ADO7_fHjtTl;4N9761!)7P2zp(TP=fs&@% z2rQj{iD#*+Cga-2CeyvAPj-ObcA`OjCDQ=>3TL>}&aAGNYa6zh3Gq+MC~)I-8i?Oe zulJDDA{RcbfS+4OdIZPP`b@sFk!$-%O}#A}3K4EQ%_OcHr59PpwP#p+IXp!PZaD0km$w5sU+f zcSXdhMBy1>H<75xo8VN9(0Ah90`Y3#`*FfXrBE1FMtkCxyywO^T%o9p)cdd@dWJ$M zb#0ctE7SYwAANI5xILKir=oe}L3GnsOsujPv`3NVlFPQIrP%bTi*@rv6Ph14FGK6B zFj}IZxwP)W!UL^Ke#bx|?2T2Q7~2Cjol}Mp@$GclT|_TG1Rhd1p9Js|TDXpVa$4S_ z)*t=BEiwG?7Dh6-xSUr<%(Zu*ASN_PX&*5!X{!glxfm?&BwmSk2l*;v*}W|ZNa zptrk+ecNCV!)XtX^$f06@6AR_i_=v!!B=a5MZTW>Gul}U_fGf%43DLw3`77&uU&Wb zy%@{}T%2m>R#RQ&v&;Z)^ zBET1m`=13qQSJgyarvDLmcVjx;@t`l64ZEd`I;Rr66}-~Df;;*R==-zF z1$QY|uKT;!lT+#TA}EyM&?jK&D~draYK^=FoY(glEiA@hm)aTW)TThrK5hv@{Lg&w zTfU4p|pfkMmGpm14$p_1lFj=0jOQevLDk%HJ zKAY0KmluLIZMxl4!MQ2h)~I=q9=}HppU9k8#0+wTd9E46ss!L7>=HoWG-#tnl#3o^ zKjN_~OVM^2OOd)u#V#rfQm>xthBE}FbV!w?Ly@U3VSOMBlO=1r=y9}rfN2Zz7_0fB)50Ra&Ok-|x@5d;GP zfrbD95&AFUErGjTJ0eN3{#3#eix~T&&Ba0AN~GUZ8F1p8zxDOlYB6g3K7;(& zo(!i&GoRbC{w~Y{Xh1KqedOOK6(mLN}03N&Dum}q~1DAW88U*E^7gro9 zW~t}$R%-w3Ck+5(RqQKskBH4}N=9ApR6$cun! zPRiWwK<4aT#{Z5mI@;d`@n>S*t=Wbsnk^4nX`jIC&iXvECz5N#L6s^1~jE4s;Bjn~aF zIfzeAXQ9dg=P^RO0rzF>WExgaidOH^y!n2x!AhVNW^A1=C;5KBmw?mU-jDZ!y>E~^ zp}v)*fpNyOffgLgk;E074y-W`Lhd_R`s5B;Psj6)xL@>^tQDjU2kHPqRDZ0lPN=s9 z)qx(I8*80;XYFA!@d?iw%^TJy41YkzbBsw9s_Sx9subHnhG0#9l>%|#4 zNeJV}J{!x(RD+0+cgY)#FT|-UfzUvlUlOq>VsG~$ohvH27{35{!)lqDhOUoPuo~*n zI(f%kr%A`x*|4YJ-qW)fSf}}^Z|P2*NKI$OGwGLH9+UcxY)on0D_#LhLZ!I;#oird z8QXkse;EhaTyd<#K=95+1;O)ptP0_GpnxUg1DFaZHL!>U_&Q>f129auPuu>5CL03* zwbnYA>C7~B)bjvK9%e5Gxd6+mo7Yt;Cp^f*>~Lm8|1fB%zA0)ZN2( za>Meg*j{lzb{bmMq8s%BtpBpPdYWa!nj%W-`Z(>?%i|{1$MY%kAYFhFkNg9wH~Cgq zDGkqQtL4)Fo>Wsvljp|UIVcy68j?J|YP*WZV7!Xa$s2GJ&jDkzKt3Eon=Q7%Q4mQ) zharJXn}d~BcN&{(lIS=L5y^}(A~`<)4;x!i+)d;OaaOeXr8r?M@V#B>18z3ziAcxS z9y|Eyl_|BQIc8t}oKwV%S|bP&)tpXG2S&BLC^QTf`6=ZaXQ;8q5gjdKGED?&KHK+C z@C&-w6C}VANYIyi_A@no~1hUt?=zM%JC(Otq8?w_jwIgQb9X@^^^S(k|pY>v1W=BF>-{ceOHx zK9Q~1cYZRvoeQF3Kws*`Sq6?@=@`AkVwK((TI0gOyMz;)Fc%lV1%XoNMYK;Vw z++TJLDNJ482E{``Rh+*$4-aY>DfFarNyS4d<&JhNda~F%D2N8~rE=cXrkECI=L728 z!EFNLd$%P4#PbQD8kjWwQOA>sfp1G$M=C=`GaRH%UG+)SmPMz@d7R?)?RA?w z4gVDX%F^0zk*t1=lKax8BYhXC(}4AOK9p!f=jCwqwDRoF%6qf)yj{x>{zBMikb{ah z;gB2F1rPAnt~xFDM!uy#2L~rU>cuu}!DC5Q&ri0kb`M3R%gU$&s z0Pyu$!0r9Rc|{ygG91KOsisiqTUwaxMWVU+Zho?w^dJ5wr|6e{$&xEq7V|Q~O?E0H z+%6m`FhVzV3v81kYitTr2hM)vAE&}%kS5+{*UzFL-on2^`WW%q1E}_HCx~W?n5Zuj^NmJF9$E$Kq{;qNAQkI0xMw7KF0+czq zHK$1@u_!R9-{^S4#s9dmx2NMO^;hqe(?ng2c9Jo2N=wrJKAhzfZ||b%R^09mb#iz- zBGf5Mbr{014uILu6GoNWGapFxSu$MC4sPkY<(fHxLy?@#_gZwFj`i<>3XU0DEGc9!xI5 z2ly^D>2hN7CIJDpaXbC90FgnW02GSS5J5#FjD6g&*IXfz{w3}+nC7?j1ArW-b=;T9C z(L!J;K-pnA3Kyjxl4BJt7j<>)S2m+P|CpA==(V?{GcB6ZqodGoO6qrS1hmKKXEdzd z)XcsJzw~Y-vo79#1oj7t+-x6j`?dY>p6vG z;^v-JFqxAFO>NR)Lfzc!(+%W3>K8gz#o;@F!~eEPV&aH4$4-6E=>7A@rriV6BCzh? znq7l|)5mUMGqtIfX#ftzMGm_jy(4(R<%vHYj!APdrMe}LdT;hCor*%IewD#%Hrw@& zzIn{)su2kZgl;k{ zV}SaCEzxV3NuO0DbO+jxK@s@}4t!28cV+%rR~^4Bnby1`fPjXDjZ{WLyjzSq=rY{o zv1Ch~Y*ZVmGFV&xFkrXDSR&AjXmi{okDM>z@>4G>E*jdD2=BB52#fp`>UG&8ebmd= zs(ecC5tt{nxAOXrs4u!<^$m~r2ocYy<{sZ8Fa@}F-eYun>Cq=@|IhN^9V>6I`fNt@OGY$m1fc1-TTvIwX~W~sK4n4o|oe*vg#4mxn+-# zb&xbBB7ldGz`>r@COOp9fgA6{Dh?}u8mbVp+S*{W1=w3@FRAEtx-8#~)-9bIuUX8D^HKp6y?mj=gK;4&E;7o8ChzwZ5FSqK=jn6v!KI*W?79> zQ0^Vj2XHe4D)DrZwIP48)&{W9I6C(un@$yv$Sf>6@F9^^t;2S)G)G{M_mmzuB6~>*+_oO z($@}p zKR)5!d)SaTBHauLd1Q~>F23&I^+!~FEMo{+s2B*AgnsAA@L0Dh)ZW@h$=^0NLAeSM!m=>GcE7RVYUe0x;3g=9I&cIP_E=( zW&o#(ykuvLwwbwF_mQKcuU>;nW|QA=ND#j_TxChy2(hKfy``TfOipdh=7SEI@>dhs3o#mB%##zEe6x9WcKP6meJ1)Fja-7^YB-HFfR?#B znNWTtIBYs^_;H9a&p7SDOz~K|t$)r~yWOWy+^q~hc%U8+Cb;@n8;KQJBQpW_i>16K zWN3SK)Be83GUM!J&G}T!nn36|>nEe*83M9&P;8#%j9y@%pSKF;6y=J~g>~PS%4j5J zJGW$a-K#Z(P)>#&EE@MQ{-GfeG5xk!PEGWD||y2igdwFE#Y(;Fvv^JMX22X z>kMSTk0fac6SXW$}Wzv0me@f!-De&mGv>F^ytxOa@q{-$>GaLa1;UAUZRZ7Sf1$Z7p4 z7}`H4%<>xXNlvx+Iu0ca`SGX>L1xt>#whAu8Xs=)HM}s9No+&r(9M93vmraaUl7d> zpqibNJgF#qiRG9p4%V9^6|khiZv5~a760UNlsX{dP5%bSG9RQBlGxupe!`c3>90LM zyFdTBx=o0RBn$}1`Kq3Jb(@YA4v~)DLl_odwL`%s0hA}hq-fYYo2C4iVj_N%j%agq zu#<8ie$SFlYp}qMhk#@XCt2cHi%g+<0;j%f!|RmX-7OHjxnpz z@M!|aOw&**MBQVbej)b@xSv?DZ^KC)!s$4~2|<9_L}Is(#LF-IHuvEhLw+|-o~&sl z8_mv?oIE({rZ20|pP{SN+VCAEQ1;v;^~|a31074Lm=U}y-*ZuBt4$5ctEbVXu~AmQ zzeAlr@9i8lc6UZ^P7{zmXbz`cPiY>53O=s_r4$;Si@As3*X&119RuHF)}DRR){ehD z%fbOl7qSl7uDZV|ANNL^A5D2;1mO7sN){%FJ~{=dZ!xK`{Vuk^X(CxBuzVdoJbUMS zPA)IxLxak8sW&t-ZGo(5#S5V};F`yY6$sSI399U?X4i2w8S`8iwT*ax4TBfk`MJ$^ ztU-@_1qeO)PI9?L4?4Dow6&D?_yVYI-|zuHb3|#bZcGHI^bC{aN`@!cv;OcoHZBW3 z(I!xQ>V1_@BeflFC8FEr2HI|C%ea#FkzjU`9zZwo-4Kf)F&_M+$MX5V0y%U8OFqEz zn;W->Uetl598!Z1n|~j`a!BZ&2b~&TRk5wEY3ZIYT_Dwa0MWFScKX6KSHJWv-YnxK(jIDP*zv0_SGrh&Tol?j`Lx2&j3$G5eS? zDNs5!ZCnj-`Pb_sgm`RM^S0u^eheWhd143k;!MG3IaVQ)CjFM7C^hR&rKuK@xhqHX zu%PG;@|6=+8MGU9&*;t~7>Es+E%CC7+Y7z-`!m*_tWC6h=69@ECv@v9bkCqw;I$-J z2BP<0uUg;e2>!n7KSn6_+df`n&3VU99 zzNTsNyyS`XpZ>HfB~(p5j9Q^DnOu>nd%>{vZ;s?WH!Vxn?4#_$;;LGJmKtQ8S}m}h zyHr=R{QntzwWZYWoz7Dx?>wsmsqjr?hmJy`H^H!rUAD=k#1*FHH(f`6O)_10`6v|< z5*O6<0l9Kn$r|BGyh}&kf<$D)M8?lNniz|#;wcGhM1xsOlx}IsE}ZA?q>SZQn=;s{ z$aSg@AA#5x;*3A2RdySUh`@bK?#M?~H^t^{1x_j$)PaiNf^VJVt zi&>_zFZZ84%j9De^VhCJ+Cw-%;FdrdgUM(Z)UxrAzINst028Q}OLDalgiZMaJybs{ zg_a>boUPI}T8dw)*#<{$gk`*(eZ?gsoYa-Jg>8-@O8jzeb|)PP$;cy*Y{|?;rY+c^ ze$c*o^Tyz_;YKH_*m>eR8puDn(mAPGgk0?{TGNYqjPiD^40S8b|1AjWN~tT|tj^mR zr2A7|eSQ-GJVFMB9E)`I9zmL3o{l*BL1=0;N+4~YaMB72!@xNOM6C$O0aA5rlw!=Y z6++eJjB@@t2Wrxvu#i(BZ0pQdU&wwKpN@T@-m=FpRdYZ@+d& zW%bn5FU3jnLsdByIyqz;xg{PbVc}(~Q_A}+L@5IR2TaxcqKM$sm8c8u^)EdXjKd}k z5BUke5DJ5RgLtK6Ert21ln85&6g$fcDtCJ+Z10@wJhI7)loxSGUkp2?G+CD~k&>EW% zh`IK3bAUoB==UjQxn!nLO(PNagOs_TR-=9Pl?ue6t;AoE#f^RhtZ5)CM0zkz4CQO! zzqLu98pvHbUL&nz+&(sHe)nG@kGbEDW zZ3JT?h#&p)kNqbMw1p)Zo;h;V-k_R+m?t6`SNgSihi#em$2WKAhEjeX&Yf9oQe4=O zUHDR&!*?#=(g3gMUePU>nJUE8i?b&y1?i=#@9z{kZ5LDQAs4hF7IaMd>OVD9q&?{wNbLo(XPJLwfM|9t zkf5KR6xCvqLSU!JGV=;zlNm>VB+P`aPrU)z2kk+i8qbCDd>iO5o;LPu`Fl6qK&J6I zq|YA9kQs7=WTM{C*5n6`!jGgm&l8Hz1d|gXK6sS3NH=XBcs*gMezm86bov`rdy4k9 z>jotKHqH<8COo1K`tpe02>+JJw{~t@ZAsOW!=1>*AxB`qih=yu(WQle5#=uk1Cw#2 zWbtwsMvZ?DDf|CuGhaYul1rBcc9h3metcLiL)Md(%oxu(R>k^3Mc^9!C`keaCM#K1 zg{vU)^YYqG=}^9~a5SF(Cc0^NqFQdHm8y85Yar1 zfoIyguCTA7%Wm#yP+$`P7^tA&?0Wr@Td*OziHicH8y`KIAOCgo2QUGp9kkhRH9ez3 zBKaKMGX~yzVLt5cY_=+)(n((>p`T8{J`pR4YCtmN3k{h=j4_@&pbp#^!DOyHXc<_r zG5SmPj($V&<$MR_!?U}cCp=MmJ7aLQoz_cE{;+>R_8L-T_ZL$Ga5`BXF7eY0%6zXY z`WN;{OLRKY;iv}tR0eTCpOB-1>8XQ>w0-4L;L59}9+Z72MIQoiqS&{VA&+f6M1v{2 zR_?Nw8!K*D`M`ai+@VD)eWvMDHuqGV$j@NzJg<{D!mscjUy`Z=K?J@~X#YuzjH&Cc z+sI!oooF1n_CS6E${au8lS-+U)(16DDQ%k?x+Ww?u8hkUJ7so@B?8RkmYF#H)$7UD z$}m-jzp6}9;uxKkTJ4AfENh$BT zP-B*tSh`asnMxUdl{!}LwB+xcVIJfjJ70pjYM<4*H65)>nPMi%mBflTa&}T&I_i!P zjdlddYuFXqqN*jv>{5MK%^v{e27utfbmxRTl;*NYW|CVEzflZD zgP8)Z70aM;@*pH8c&6GyXbb|=%7L!Hc=t42{y6+hF}u86Sa1v6q#l%W7&04Nx#m@H zRWhJte1ka3mSenPX&6H9h)JE6fYI1!+e`;R+(Quh5HMDF0kEQJyLa1g0?=PwXQbS zGTc63X|XVQGx&z=SP0i>(O^n$YFT$DFUfHo;xeVraR%XXBjoa;>N0%BbvqwS+W0Y< zJ>Ag?%}QOn>>wq+t2C?0L-;gV=C&7sG^}e(PQ?y~cP*{0;EbGleX8leCq%qG6hyo|0*55FeT5;md;Q%hJZc3ev~~x8?9;eQ3valO3wLr+H}+eIdF0*V zwmNoQ-JqiNj?LTY)azF%s8#45sMmaCntu;65Nq$R^xxoi{rrB=5FbwKV*ao(hk4TI zDNDOun{rGTd7Y}8lL2U)f(+HGzz{E39Z%&WTuz{ecx>4O$FKK}zsOYq$n;rY}R(~v|;_R@LN{iRWjQz5l zJ*p9i<=lP@*>L3A>dW46m5sNQeFmAs_oo)eCjh?A^ zEfvx~{3qJx??0P9tzX;}f_PENV21VYF&FQDj7oMzJE<&8krpzIyWd?Gx$!Ay9ot_!tQo z$tiw}9LuRbRV6eMnVlO^4|5FRq=0=|;>KxAv!;j%bz3Rvq%965iG zaqWpQGU_}2gw(Y-P9KpTI%P9BYcBwp^%AQMN1o9r7bLGH%&b(E*@>I$C9lW`yQ#&; zv#G$pyc(^d5g7{K=12g!Vd(2J&Oxr9904TxV<=EbBe=f?Sk!Ptp-#_MK;l@q_0JMO?%>?zq4UjP3 zNKZtGt(d!ph)QLVFXI1g`81hVEcyNi9bu=4RB!=yl$TU+#tQy;(9;v{e~vMy(wdPF z5}QVv#!^V76k)T=-`&&_NENmGxnBO6z-Qt}vgWv7g#V#A@>7DxH-k#IxaD>7%isIx z5v=%nd>Lm3f!Eovh%iVx^BqUykwtt*!BZD#k&N=ntlQyaFv;E{O`^V}IuCWhQF zERb}dC!%ITuC-yP&q}1!rnqA+h$;YfSSwqq9L0r%e1EkQ+_(qBzE8|er?X9J>+^uF z#G3~^51(1;{hX&c63)-mIK%@i%{U*eq3OiynLy>aI3^{icFKqmFBTs$1xsII9e@j} z*GJ@%75X0k=6(Dtzgk7qqnv<8%f^nWXEx97X>#-uj(WE;F+<3_)Yt#DB60qIG~5{r z8SEA=2*?vB2nfml^Z-bJ|1ly@XCJ6ipv_4K8j)lu%q^tRB*f7uG9eUn5Oge(dnig$ zvRo{|Mp`|pimmHb^;$eG#p>`|ID^$B?P^xrZ0mNeZ0%kG-t<~+`+hoaXQiNie_teQ zzw8~!o$vPHZv6NSmU(aX#rxF|w^JFX z!-y(yak${&q2i6b`Cceb__9pMF*yO+eC5>qFq9}z<}|`1aAY{a*SkNK=x!0@Kh2!k z+lLLHdOH4>_o_^kXyL6e!!zm~dGb+y(SGHvF{6JbLH_Y~2Gc1iGV%RgieX@MfAYBU zqjngepuy)A*GGsxN1)oMUhKP2aE!_xTX}Rno^##InEivE3tZ zL!LMZvi;u6e1)EDz^nHcx$RaQl+)hQ+lPXb8&5vWZ!yF#%NNNDcKb0rv0(4*SoSx( zoYiF~+YN6~Gq>@v=aZEjF0KZrZe#JUu~r3uBqKJICxoq7u(Oe(G#M7c%L;3pT_jAe zv#A>ihqdo3w1V&W<~uwVqd|E4GZl)>h4G;!bnfWjUnI%*_Zgg}mUNlghH_@CGuSLn zezRcf<=B;$O3}oBPZFNt;z=UNOEbUFbMUWSOi1x$p4LKtg6k7i@>A>v!)JJPI99d+ zD};El&|aB#A*6r$y0>D-^}!E<4(q6}!^+K=KzLuKjCnG3M%@E9T>kR)QQXcxf6giZ z!(|dCIf^K8xVE66yJptMK%hnil5Rw--AJ%fow}E@&X9*{m)vgbK|#0uy3H&}Ot_$q zV9POiDc4N}O|Ey1_ly}5VWfsU9vLM75agW2=&X`nCp=4^=v0tVzKsD4>zp)uIi{rL z_gHU@S(O{hyRo!kq^M1-X8Ay239spe9R>Ma^&GeH+99MamIa*HGA`w5%gWhysFDZ7 zJWymyjdaoeuwKjJZsDBJS&I#f<}hB{tKXbR5V7z%mdZV>$0R+NfN~t&g^_1KQMP=@ z0$yYGA?jy_`9*t)FbMKB4grX}AGk57`%`9=OBC1N1&X(4G8WP+o>l3XAb|!q&+DE+YY3dMlwR)4qSYFtGz*nt-z9{X zs>3HQb6=H$n&AMbw_#!=b5G`|P~Vjnd1|K~tx;pU$uT6OYIj5^x}5$^z94zZ<%WqcMDX zWLa#3?VN-ny>2`P6>{H!>B2eqn?L_73V}*(U!`SL!W2AMLQE0>Wd);)mQrF6X^gwM zff6J0tt1AP8Ow&5kf)O2Dzvgi0JX7hjPX8=0DZ*p$SDB@1!e-WIZ!Gy6W#&hjvv_qGrbX@Pp`~nr}8x}CL-2og}PeYLH0DGa#rtgsIBtIhqS8% ziKxI;8VOfZWv2&FPejr+?Hi^i;H#KURZyT%cIp%?FmI?124nWCQy_PP)h!iejAyGz`+&75-w$F}_j7tc>3S!(R&G&3kr$v1z!Kr?TuaxQ@i-k)lL4^dImINWY zdGIIRP@>WZgm26rz0nro8_U}Y9 zX0BJKD|WzH285?!*so8o?;6qD#Y)jVYJ%u;qHZqsGf7`sUh#v90G3=Bq|7Q@=K?cx zcJn#&Viv#8ET1uEmkp;o;n#x2PvY%O;@FR2)Sq|}n)M@SY+Jcjn6HM?9xmto&J~xm z8^rJSvm3;u6V*U;4c8148Voi}OAphi%vaUG>HYt3C$N4-Dkh+69jOVo1eCboKJ$Tg zS$V%O%I!-@;r-=j{NU{aO|g4xeNs`=-Q^(Gy-dfdlXZ4tKBL}$yBdnBWmB{cP~{0s zl`4c$gTr&L*f=V}#}LtNyG}rhq@t$g+1>HPhhj&B#!5;{;?NYIGW&%+_s$yA*TB-$ZaIF9OUi7PfC}YLvearoj5$^0Y<1LVxokAvnZ) zM%T}Bwy0felzZGVHu0E}P)Vpaf4J3x9sB5IiS`%m;cfcY<#q!8r0NYiV*dL~+Wl4Y zxq5HyY16^69i@}*1{5)d@!m0c?^a;HQigykKE(acR+RvF*yup6tp^D&5X%&@egPg` ziAGP}N}6D>sKP%{Ok|#$O%>A!?W(U`&K4gWU){VYsG>t#{mi3;QNZnRGgGgxj=y(x z%t5MUN1%>OA!-x2>OzZkN4DpmgwwS5Wahto;Ew9?_mh>!867Sb)LR@Ep z>NRon%Cs!3SM(GY6*XCE6Gp+r)d1hPn_w>4VS1f|>JS&Z^~UQ+ZIx#6n%x!$oVuUg zT_4b7G$RDl8BClll60OnF^CRP;d2&XCPwjegYmkXuGbt{{bVt78k$j!qAiienp;xHDmFv7OZ26E4EhGs-^X1tFQPL z$$LSdd!bQ*CWX>uta+Arakra9%rzAAepvWw5hnXA|DYPoio2^$E=1W$aZ3NhWUoQJ z8Z|w>!&QQ$r=Il9{f1SFIlxn$a2A$UmE1MLCpXm2l5MJ11%8}_ciIsblVxe-mqiVq zXkoK!8Yg#+x`5K{#t?SutnmBE%#Wd*dDp)A&DVF$bZc=5kET^V#-H;XF6NUmrXZ!h zS3xOSf40#Ee&d493pYau8hO&Sfg7H9N?W{v=zXe7q7k`#*JwexD?qGYYLo@9lhXEJ z2W1a6s_Ym?dm~-NbTz>mh*?!>LYwZRLwP)RKLvI`r{`}Rd@!4Y9-9vo)@<2_Ls!-l1E zVtRHDyW#{@ee^`IhyW@DQ1(pt@?QaJx+ejWY!DM!I3Bh+o$%i`2sP4eit04Z`lp{} zxgv`XvvmBss7k?0+ZK{iXG&-zV=(GM-r7Q_H)B}v35ZE%yUo0#C}^5(b@>VJdxALE zurS?xe6=D+H%;Ibg1l9|1~tsNtk2h7Fgd@#3Ln z1aX}3BB{dvR_j@q{KFr`Y2H$TIPo|?x?iis{E4WHTx7qnxXpCZvsSEii9`-e%385lrBM z116vxn zm3*0dGhBt{wqKc!7cyVFIjkxb1U;0^=)CMCMSPvoC%L(P-pSPBzh4yk@ZQ0jkU-`n zw$(`7!ZG6I)L16VkEVyTpJ$ICBo0?)X6)gzz~?F3rZOV!0hUILRmXBU2O=_QO!#Iz z{iBDV(*WWTT*OHZHggN!mjTIDhhFBmSk|>D&Mdgeu2umo;4gmEc*Sf*%4JyzzPB@? zu8ho^;S8ylqmLBaJ&V_Gi~zoWU$}J1Im|uSlgAmpLNdrzr^8P$`J^<*xbj`%WZvQLE0mV>d0WSd{}7OhMVv5A`d>5EY4<`Fz=uhJKez(vD{)Q*?rn{8>OaW>^BD`pKnRbH`_ z=^80(xlRU_@Ja0hx48M`2RL`7;fsJ}fX*fD!p@6o(cC`<+)QX$tL3iT>Fu?%63Tb# zv(#aZKaVW47X+k$g)5Wn&}${y?3fG_I>I9Lgw&2(g5C!ZZ$fFibNg(hC<*@S#26Y7 zYmcOiF4j%?z>5;Kiwd|d7+;)1b{a87jL|On#s^c|GMqelms-41qRrq9ep-ocfHks# z9C7({s->UkEV77@_*^nHJ+Scf?9w|_OKr1H(WWQc zsJsUv$lcMC-UnNF`NTN$OEllhCRfSi#D6^NlM7cs?$1PtdX&=Wf0)TNye(Lh($Zdf z@VOIcRu+_hl40{_P=YZ#>ppr2pr>t^gSkq#5D1-yl}7oGuYc7iGn$`1qG~%oWG%Xu zHZ7{$gL!b;+5N=5#1zgJH5~Q0Um`#cGu}yFx7E5~%;D^rpaK#;K zxjVmp_+m!~K)!l47L9LmBB%gER~*o1KbOsx?&D)4D+wVeroC z0}nNTs_=_9R888C!T`x{+)>5yiz>7M1hskfu7&t27}Z_q8I5=i?AzcJ%f15bIl&>UN}+1v2~@y3 z24(W6%M9T{8;c`ac=`Eb#OucK~H;$hEj>TESERX4&q$s_iJ;f7_z8%-ZMB;>)42E1a!S#@aSTSg z|Azl*GoNbLVzA`}C{6uG%FvW-kjAW$l4wt4)3sPRCG9Yv167M`iSr8}%Y~o~iGNEA zK*S0wY$dXSPh_2Mucjrp#{^p7H%uehxFvc9)`?|LoRj{pa0s%M`g=c7_9u_;Mhi6I zth_iqba~S?bCWMAX04r13lnQTL$DQyyD9j$1|!8mhx6zLumI3_&5g$$b_ez??16P& ze@s+*OvcQ4K#=_>t3_8zcDbZ=ROfdT*uvSgRFAa2Mu)a6z=`DKzNq!h={6c?ayi}e zme~Q*?SAz*JyC8{x(jFKfJoVCR_avcr)P7Ou#nOWAwnvMTrf#IO` zdg<)t3A(a^SoAmnq&SBw4qNpFy#Yk!>05bBE>GKpia!44$)Tr$GPEPd{EoK-) z&Cg*DklOwul80AmU(tS0f>fI)c}ruYwJmiI;o74Z=zXbCmSxHih%MK+{m;Ul z;-I+bLf4fj=+UD{51qi{HqGh2t(eGQQgdP9Jk@(^;k^3d%>5&m%&Jaa4p%m*f=Cv$ zj$kmku|LHS{B4bvIFJi9P!r5s0g~lpa6tYJ@C)>@9KJg|wIwp3T8Rp>zv4g2Ajo## zaDJ|0NoP0*y{7j{r7WD#)xc>9LPu!n>LI4^x;C?LNTORJnO{~|<|MG^xlK#RE`Xq1 zh{p(^UzYh4WuXR>{(ssy>#!=ewvEF^Qo0)i>F!24wsc5`g3?F{k^|C6Z@Oa>iga%f zkPZpibh8npQyTH(&-Gg|^Vd8x_qv;&nx4ZQb8EXlV|ZK86|b1{ zfAqV01k)Qs^Q0V{*+jWZpapzDalYi$L;lK3(3=i2y1}Y-Z_?2OPQ&rbQN>w*Ck%JC3c_c@hn$n{M`XVmG{>)N0 z;}Wge(51AR47ePKh8n3RY}zSQz3)C_4j2?ya zsiiW+edY$YeR85xZ|u(rn(j0f9VhAXxcmIyUcOCnZ1XAx54wZI;XoZI$ix~|5$-T> zA`DwV+jy46gYj+$f1zuwG!5v}#UvYh!3{O$ewXu}0GoN-Ge?2!$QqXD@4g8Od^6aNAAu5ZWywAQH{&^Ue%WSjK%l_oQ+ztd?9nS`MO!5SsuWY0^uIimoF*M z&iFosh4i*pX`qN}Dyul~W}n2i;{o%_qO(l{xt5cA3kwrea&W`8 z>CJw5!(OFTIDKHfTH!TgLD$*&w2{mviHGW!o*0l=-@P#(m{P(nGWC7hGxHDl2Wn9qA|1V!B%4*2{o|S2WCT6M3THCPoYEMYgz3hyD)NaaB^( ztLRqJw=BrOYC@}M2;4Z4BY#f7)p5>mp5Rt{nI?uZFogLdtt>GC#WCeXEltO;^67_G z4SnTL6B4o12U-au0Xav^5w^#_S!Vdw+1`vinsZO{^kT?dpcev$W8N*u*C{7PvAq^i zW%H_40B1>cvq^izJ0}!|nMg}VM=(gca*X}Hc@D#=I7!gR^$l^R%a5DdUf1W}!p)VI zqa#|LX^HDyU3v6c^zAOyXB3Ggworwf){A4URq>z`b3X3dg6;J1+9mzB=8Ds$B?A5# z-{EX=qj{L7n$qR~PPZ_EcU-~dkkTGeYs*zxSiT_lc>yiXFizfx3J1~rUSzCj0T*v8 zuk5guc9hE7YCBLHOd}bt85EGhh!_s#UtF-Gd@Ro^L@zVrv`Mzz-{(k1t z-04kgHcZ0nBEDqAQMQ|rm-Ex>@y5Ya^A(M_dP!!`j0$%pvHZywTguD?MUL-PXTK6YS)PTV3|$HzR%PUF2`Hg1Y&=hCaEp`UiX97-8X5 ztd#SIQPJ~&XQi$sXYTXYc7DZ!PN$+rQDW4h9myTI8Rn0*ciQ`!Nq$VX{zk7wZ9l`u zlCe-sdwJ)XVuT5iEunw_aC8XUArTOhvZG?3T$E%*>VOY@~W~61Tb4{M$_2%t)d^wi|sSZ4!XMf)%rFs{@&+lOuf1KwZ?*^ z)}e%r;N|EtePFJbPqXpTFrtHj>%=;xpL3{6E+APg=&J+213&RgLz}!LoIapFCOE^v zkU6*kQGKSZtG=l3)qn-ZM#LPU4u7NYxDeAH{#42oolYWs5VJwcry}f}99tJT@IhxU zDd=WE^n~Nv;HXnq?2i=x8mFx7XTvW$u&`lI?DY#jHT$&XkHwnxY|;j&P;?0{7@>*O zFt%Vr70aTT=HG{Xcs-$k=iv*ONR;|CP31pZoOBG%305DF;-^MqR2-@(B{AVJcyqci zH8SF1;=gmPICNvFXWyV?c7!v}0n2Q6Gd$YG08ICAbCdebhaXL8R3rhEW@PIXIgTZa zV2xLmKcc|beM8fSUT{!L6G4w+{%L(sTP0`S4Yvc5O*nt zmk)okOR>)3v>=!EN{p@DFe(o2Yo{9ye4Guxs*!bXXQJE}&4z-!^5VV(uE>o^e3gg| zh%)Hwt*&$&{_+0lwM0=-`LD_0QzlgL{D;pUjKAAzGA4O$%;ULP=t8U_mkL_69*6Fc z*q%%^8`am;E$&6I(t zvTHa?4}HDNz#hqouHUvrrn8sNk+M&T&G8Kt6;3`)cPWA_yrl^RXrc}Cm9-*~>dA0l z&O04iF5p^v1q3%;R$fS3gN`^~iD-K|*Ws3@s_?z`!Vu~*4^7G?(aR&rrV$!pn0SL9 zi9UcK>&D9tN_ZQuJrNxmICfZil!mXcA*=VOAB8XCYp|r!Fa$r|8i}9#f!&&H4wkGQ zp9&Qoe@sR?e!LzMaIA$vxGK95LLb*nPe5x;AasczzmH@she5b5`|K{-L$6J1jU&{9 zAMb&5{D2viUgU{8eWqMI8#M*q_SRvL`On;ONxZVdjIPdfRh23ro__}zwvhYheY!@ z4H4yhvfZ$RvfTpI`SBncOOM&vuOm@&g6fcrVmk+)lH#po+A|dsN5&Z~ zD|oRz{2F{Pb0_FxR6q6?=;CjQ4!gbJJMizj2Ks!L&zGdHz6awW+mc2pw1T^{kz0-B zkj)tp^@KN~%z$i%*;nuEh-vggG=kSjF2t`BwW39y8U|W1EHONP6iWixSiJG(0Af!% zec!ImzxAvPXToc7~B_+5J5M;^zeLw^KkS%r#`ym}-l!Cy9$+5E} z{a00pwqMw^-?L7}i>wfL(a~!2_(N~W!CD@b3x+o0j#N~=y$A&>sf4}H)hY%*WL|4t z%Xn!Tc$sK^(v+$4Z+FwO1G5e!6qnlB4kU1az{aQ6b9#vzv`h)wsCTj=$Jdb!&G>Gi zi(J)a+`wug^>Yt{A(<1{DayAcTx5fv$}seT>O1k6pF-+mx{6w^hQ>0Ra~aO@Z&Q<0 z?vWqQ>gtp|>$>OAjtfcXi?F4ND2t1WQT65sY}B~EO=Pd@P88J|OVmFIHM4iH_aTBq zwFRLp;q?T_Z}wn(5P=X zg5XGh5Iogb7g4FrudD~JDZzP_#$ov_dH`xKe0rwPzrdg~IBSG_TK{{L4_}`{0Ss@O z^LIx%mWy&BU?$9|*@HPJ4uL<)uQri%=>a$njDAskaMrZ1`tt!2Zhva4` zwMS>xiN(UjuZGlQg4c9yNhJlJ({ja>4Wz zNM@`G0*2o$SLG#%k2N_{M`%l*Vy~|@UeGWuajuV(?xEI*b;%4H`(- zQ|kiDq3;D#&Ik$$+YZb8`=%C*>Fan&?vP(a4QAl%?4&uCO5XL zuGhT{_@^Jb!gCfR&A9WMVZvUjInE7ws!457l>+@=#)HBvMT;R^)n8~L)sqsS0wtd1 z{w4E8oKgmevKW_{$vWFOFIfn$KSh3JLoRY>YI{}<%L3TV=$0tS0&fA0m7+d}hjr=@ zkI4h~^VRqPI=BbG-oIJ%jz z9zJH!qhNG=9Iq2CC?tGBm`wJ(?R@dzT_FQh9gKP&VJ0Tbg!Ax-0{fFQ-0mCr^^4Od($0}Nko>Bu4Vk}yVb}#oRB^*w(>L+y#ea{|; zQgt$l#=T;(5E*L#O=}%;RO35T&xqUZs5&q{vm_F>6rd^OBJX&X)vC-I$M-ssL$O;ZYQ9Q@{ zPJo%AzM#&z^t%W?God^WYc9;GX$ie;%n6(rShMHw?Xk{haM%Nj3+LHJu{G$h$I+9h zpvFuJPQ@=fN?Ci?O^EqRu5D7-@r7;GwHyOlU%XcyN06^e+A7Xy^3&PTlciuid7uz5Q+5yl`Zxss_vKw3s%wl0F4htX+s(GQ1c zR!nGCt|JhOtIWjEsfK{s%0Vw&ML@qTGR*x@M2%A_7QuU(g+tWoSj|17I_J@S zK!k+0@?-VY0&Y9}$CclA;PNX|LTajnnN0!n&eNE+_kjk+S6-4hT2GNd#I+uaBG~`( zw`|&CC;Y$umXL%Ma_WDZ;obS+-5<~(o!5}SEh^Ig-xCk=XoU{)dy5jZ;|h7yP6FZG zRstQn-&bW^DIw`A_>jhJN|2KGedq?jfE;djVCQt9IC^|8P_J11x&?7l;cX-(?5UrvB599zuG*$nafC z5N_IixM>#*BFVk4;$>1olsEAq@_Uq^+5G#E#~vN^AKNAVn5e7Z9=h8-0U5f}N&O@C zy>l1k`U4a#{RgDlXQci^eEA3X>*HS_4#aPt9Yor5*L(ySxT|*dL8`kLAO~N_Av_1{ zpyGkMChPsp>#|2+saXn*W}1l%E-`^Wje+55lObUS$u{x_dzr2cPO@mE&y_i%S5 fG^k?sFOY;(>nZBpy&)lq-kn)QNJxuo|7raXi5||I delta 20086 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/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 21856 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 delta 20086 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/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in From 8fc9a6834499d65c77dac02820e1082107521d9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:17:01 +0300 Subject: [PATCH 46/95] Bump com.google.guava:guava-bom from 32.1.0-jre to 32.1.1-jre (#8844) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index a673a1e7fa3f..7723de7a2756 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -31,7 +31,7 @@ val groovyVersion = "4.0.13" val DEPENDENCY_BOMS = listOf( "com.fasterxml.jackson:jackson-bom:2.15.2", - "com.google.guava:guava-bom:32.1.0-jre", + "com.google.guava:guava-bom:32.1.1-jre", "org.apache.groovy:groovy-bom:${groovyVersion}", "io.opentelemetry:opentelemetry-bom:${otelSdkVersion}", "io.opentelemetry:opentelemetry-bom-alpha:${otelSdkAlphaVersion}", From bba8a073901f6ffd3e415e2570efda075656468d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 3 Jul 2023 00:54:51 -0700 Subject: [PATCH 47/95] Make gradle update PRs run workflows (#8851) --- .github/workflows/update-gradle-wrappers-daily.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/update-gradle-wrappers-daily.yml b/.github/workflows/update-gradle-wrappers-daily.yml index fcac7092822e..3f90447c15d7 100644 --- a/.github/workflows/update-gradle-wrappers-daily.yml +++ b/.github/workflows/update-gradle-wrappers-daily.yml @@ -21,6 +21,9 @@ jobs: - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 + with: + # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows + github-token: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} workflow-notification: needs: From e7e308f0464faeecbb2e32eca7bca1537c83621b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 08:49:51 +0300 Subject: [PATCH 48/95] Bump io.grpc:grpc-bom from 1.56.0 to 1.56.1 (#8861) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/build.gradle.kts | 2 +- smoke-tests/images/grpc/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index d8e58387fd09..bc316046bbb2 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { api("org.spockframework:spock-core") api(project(":testing-common")) - implementation(platform("io.grpc:grpc-bom:1.56.0")) + implementation(platform("io.grpc:grpc-bom:1.56.1")) implementation("org.slf4j:slf4j-api") implementation("io.opentelemetry:opentelemetry-api") implementation("io.opentelemetry.proto:opentelemetry-proto") diff --git a/smoke-tests/images/grpc/build.gradle.kts b/smoke-tests/images/grpc/build.gradle.kts index da6db1b70bac..875f7cbfaaa1 100644 --- a/smoke-tests/images/grpc/build.gradle.kts +++ b/smoke-tests/images/grpc/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } dependencies { - implementation(platform("io.grpc:grpc-bom:1.56.0")) + implementation(platform("io.grpc:grpc-bom:1.56.1")) implementation(platform("io.opentelemetry:opentelemetry-bom:1.0.0")) implementation(platform("io.opentelemetry:opentelemetry-bom-alpha:1.0.0-alpha")) implementation(platform("org.apache.logging.log4j:log4j-bom:2.20.0")) From a410cdeb391ded28fd1f2f8b11447fcf8b9cd16a Mon Sep 17 00:00:00 2001 From: lu-xiaoshuang <121755080+lu-xiaoshuang@users.noreply.github.com> Date: Tue, 4 Jul 2023 16:19:20 +0800 Subject: [PATCH 49/95] support for adding baggage to log4j 2 ContextData (#8810) --- .../library-autoconfigure/README.md | 6 +++++ .../OpenTelemetryContextDataProvider.java | 18 ++++++++++++++- .../SpanDecoratingContextDataInjector.java | 23 +++++++++++++++++-- .../logback/mdc/v1_0/LogbackSingletons.java | 4 ++-- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md index 5d4c761a3106..17277d87ffcc 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md @@ -42,6 +42,12 @@ will be added to the context when a log statement is made when a span is active: - `span_id` - `trace_flags` +If the `otel.instrumentation.log4j-context-data.add-baggage` system property (or the +`OTEL_INSTRUMENTATION_LOG4J_CONTEXT_DATA_ADD_BAGGAGE` environment variable) is set to `true`, +key/value pairs in [baggage](https://opentelemetry.io/docs/concepts/signals/baggage/) will be added to the context too. + +- `baggage.` + You can use these keys when defining an appender in your `log4j.xml` configuration, for example: ```xml diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java index 8aa905c02937..459d7085fcd8 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java @@ -9,8 +9,12 @@ import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_FLAGS; import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageEntry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -21,6 +25,8 @@ * #supplyContextData()} is called when a log entry is created. */ public class OpenTelemetryContextDataProvider implements ContextDataProvider { + private static final boolean BAGGAGE_ENABLED = + ConfigPropertiesUtil.getBoolean("otel.instrumentation.log4j-context-data.add-baggage", false); /** * Returns context from the current span when available. @@ -30,7 +36,8 @@ public class OpenTelemetryContextDataProvider implements ContextDataProvider { */ @Override public Map supplyContextData() { - Span currentSpan = Span.current(); + Context context = Context.current(); + Span currentSpan = Span.fromContext(context); if (!currentSpan.getSpanContext().isValid()) { return Collections.emptyMap(); } @@ -40,6 +47,15 @@ public Map supplyContextData() { contextData.put(TRACE_ID, spanContext.getTraceId()); contextData.put(SPAN_ID, spanContext.getSpanId()); contextData.put(TRACE_FLAGS, spanContext.getTraceFlags().asHex()); + + if (BAGGAGE_ENABLED) { + Baggage baggage = Baggage.fromContext(context); + for (Map.Entry entry : baggage.asMap().entrySet()) { + // prefix all baggage values to avoid clashes with existing context + contextData.put("baggage." + entry.getKey(), entry.getValue().getValue()); + } + } + return contextData; } } diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java index d597faf36e32..544d1cdd16be 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java @@ -9,9 +9,14 @@ import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_FLAGS; import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageEntry; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; import java.util.List; +import java.util.Map; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.util.ReadOnlyStringMap; @@ -19,6 +24,10 @@ import org.apache.logging.log4j.util.StringMap; public final class SpanDecoratingContextDataInjector implements ContextDataInjector { + private static final boolean BAGGAGE_ENABLED = + InstrumentationConfig.get() + .getBoolean("otel.instrumentation.log4j-context-data.add-baggage", false); + private final ContextDataInjector delegate; public SpanDecoratingContextDataInjector(ContextDataInjector delegate) { @@ -34,7 +43,9 @@ public StringMap injectContextData(List list, StringMap stringMap) { return contextData; } - SpanContext currentContext = Java8BytecodeBridge.currentSpan().getSpanContext(); + Context context = Context.current(); + Span span = Span.fromContext(context); + SpanContext currentContext = span.getSpanContext(); if (!currentContext.isValid()) { return contextData; } @@ -43,6 +54,14 @@ public StringMap injectContextData(List list, StringMap stringMap) { newContextData.putValue(TRACE_ID, currentContext.getTraceId()); newContextData.putValue(SPAN_ID, currentContext.getSpanId()); newContextData.putValue(TRACE_FLAGS, currentContext.getTraceFlags().asHex()); + + if (BAGGAGE_ENABLED) { + Baggage baggage = Baggage.fromContext(context); + for (Map.Entry entry : baggage.asMap().entrySet()) { + // prefix all baggage values to avoid clashes with existing context + newContextData.putValue("baggage." + entry.getKey(), entry.getValue().getValue()); + } + } return newContextData; } diff --git a/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java b/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java index a14cac81706e..9a75f8e30021 100644 --- a/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java +++ b/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java @@ -5,11 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.logback.mdc.v1_0; -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; +import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; public final class LogbackSingletons { private static final boolean ADD_BAGGAGE = - ConfigPropertiesUtil.getBoolean("otel.instrumentation.logback-mdc.add-baggage", false); + InstrumentationConfig.get().getBoolean("otel.instrumentation.logback-mdc.add-baggage", false); public static boolean addBaggage() { return ADD_BAGGAGE; From 15337549e3160b6db4acf8ea5ce39fb95a6bd63d Mon Sep 17 00:00:00 2001 From: Steven Swartz Date: Wed, 5 Jul 2023 03:34:22 -0400 Subject: [PATCH 50/95] Adds spring-data test verifying error spans on JPA repository query failures (#8857) --- .../src/test/java/SprintJpaTest.java | 6 +++ .../spring/jpa/JpaCustomerRepository.java | 3 ++ .../testing/src/test/java/SpringJpaTest.java | 6 +++ .../spring/jpa/JpaCustomerRepository.java | 3 ++ .../src/main/java/AbstractSpringJpaTest.java | 54 +++++++++++++++++++ 5 files changed, 72 insertions(+) diff --git a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java index 93189f602b9c..7ed8c87ee335 100644 --- a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java +++ b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java @@ -4,6 +4,7 @@ */ import java.util.List; +import java.util.Optional; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import spring.jpa.JpaCustomer; import spring.jpa.JpaCustomerRepository; @@ -52,4 +53,9 @@ List findByLastName(JpaCustomerRepository repository, String lastNa List findSpecialCustomers(JpaCustomerRepository repository) { return repository.findSpecialCustomers(); } + + @Override + Optional findOneByLastName(JpaCustomerRepository repository, String lastName) { + return repository.findOneByLastName(lastName); + } } diff --git a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java index 53ab7ae12df2..990ec4f101f9 100644 --- a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java +++ b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java @@ -6,9 +6,12 @@ package spring.jpa; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface JpaCustomerRepository extends JpaRepository, JpaCustomerRepositoryCustom { List findByLastName(String lastName); + + Optional findOneByLastName(String lastName); } diff --git a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java index ffe7702bcd56..a088c8589b6d 100644 --- a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java +++ b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java @@ -4,6 +4,7 @@ */ import java.util.List; +import java.util.Optional; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import spring.jpa.JpaCustomer; import spring.jpa.JpaCustomerRepository; @@ -52,4 +53,9 @@ List findByLastName(JpaCustomerRepository repository, String lastNa List findSpecialCustomers(JpaCustomerRepository repository) { return repository.findSpecialCustomers(); } + + @Override + Optional findOneByLastName(JpaCustomerRepository repository, String lastName) { + return repository.findOneByLastName(lastName); + } } diff --git a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java index 53ab7ae12df2..990ec4f101f9 100644 --- a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java +++ b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java @@ -6,9 +6,12 @@ package spring.jpa; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface JpaCustomerRepository extends JpaRepository, JpaCustomerRepositoryCustom { List findByLastName(String lastName); + + Optional findOneByLastName(String lastName); } diff --git a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java index 0455ee4865bc..055a0c14348b 100644 --- a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java +++ b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java @@ -5,6 +5,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static org.assertj.core.api.Assertions.catchThrowableOfType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,11 +16,14 @@ import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; +import java.util.Optional; import org.hibernate.Version; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.jpa.repository.JpaRepository; public abstract class AbstractSpringJpaTest< @@ -42,6 +46,8 @@ public abstract class AbstractSpringJpaTest< abstract List findSpecialCustomers(REPOSITORY repository); + abstract Optional findOneByLastName(REPOSITORY repository, String lastName); + void clearData() { testing.clearData(); } @@ -319,4 +325,52 @@ void testCustomRepositoryMethod() { equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); } + + @Test + void testFailedRepositoryMethod() { + // given + REPOSITORY repo = repository(); + String repoClassName = repositoryClass().getName(); + + String commonLastName = "Smith"; + repo.save(newCustomer("Alice", commonLastName)); + repo.save(newCustomer("Bob", commonLastName)); + clearData(); + + // when + IncorrectResultSizeDataAccessException expectedException = + catchThrowableOfType( + () -> findOneByLastName(repo, commonLastName), + IncorrectResultSizeDataAccessException.class); + + // then + assertNotNull(expectedException); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.findOneByLastName") + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.error()) + .hasException(expectedException) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "findOneByLastName")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + } } From 2dc77a261317d167eb048aa5917ca1ab8b56d90c Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 5 Jul 2023 19:52:39 +0300 Subject: [PATCH 51/95] =?UTF-8?q?Synchronize=20shadow=20rules=20in=20muzzl?= =?UTF-8?q?e=20check=20gradle=20plugin=20with=20the=20ones=20=E2=80=A6=20(?= =?UTF-8?q?#8871)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io.opentelemetry.instrumentation.muzzle-check.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts index 23f5b5ed1fcc..19238cccc7bb 100644 --- a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts +++ b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts @@ -96,6 +96,7 @@ tasks.withType().configureEach { relocate("io.opentelemetry.api", "io.opentelemetry.javaagent.shaded.io.opentelemetry.api") relocate("io.opentelemetry.semconv", "io.opentelemetry.javaagent.shaded.io.opentelemetry.semconv") relocate("io.opentelemetry.context", "io.opentelemetry.javaagent.shaded.io.opentelemetry.context") + relocate("io.opentelemetry.extension.incubator", "io.opentelemetry.javaagent.shaded.io.opentelemetry.extension.incubator") // relocate(the OpenTelemetry extensions that are used by instrumentation modules) // these extensions live in the AgentClassLoader, and are injected into the user's class loader From d875d997c139b0eb3fea54fa7c092041fe475de6 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 5 Jul 2023 18:55:46 +0200 Subject: [PATCH 52/95] Micrometer bridge: interpret no SLO config as no buckets advice (#8856) --- .../micrometer/v1_5/HistogramAdviceUtil.java | 12 +++++++----- .../v1_5/AbstractDistributionSummaryTest.java | 8 ++------ .../micrometer/v1_5/AbstractTimerTest.java | 8 ++------ 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java index 28167918957b..8ae5037e82e7 100644 --- a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java @@ -13,6 +13,7 @@ import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder; import java.util.ArrayList; import java.util.List; +import java.util.NavigableSet; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -27,22 +28,23 @@ static void setExplicitBucketsIfConfigured( DoubleHistogramBuilder builder, DistributionStatisticConfig config, @Nullable TimeUnit timeUnit) { - double[] buckets = config.getServiceLevelObjectiveBoundaries(); - if (buckets == null || !(builder instanceof ExtendedDoubleHistogramBuilder)) { + if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { return; } + NavigableSet buckets = config.getHistogramBuckets(false); ExtendedDoubleHistogramBuilder extendedBuilder = (ExtendedDoubleHistogramBuilder) builder; extendedBuilder.setAdvice( advice -> advice.setExplicitBucketBoundaries(computeBuckets(buckets, timeUnit))); } - private static List computeBuckets(double[] buckets, @Nullable TimeUnit timeUnit) { - if (buckets.length == 0) { + private static List computeBuckets( + NavigableSet buckets, @Nullable TimeUnit timeUnit) { + if (buckets.isEmpty()) { return emptyList(); } // micrometer Timers always specify buckets in nanoseconds, we need to convert them to base unit double timeUnitMultiplier = timeUnit == null ? 1.0 : TimeUtils.nanosToUnit(1, timeUnit); - List result = new ArrayList<>(buckets.length); + List result = new ArrayList<>(buckets.size()); for (double b : buckets) { result.add(b * timeUnitMultiplier); } diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java index 324adae5063a..44ec09510727 100644 --- a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java @@ -12,17 +12,13 @@ import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Metrics; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import org.assertj.core.api.AbstractIterableAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public abstract class AbstractDistributionSummaryTest { - static final double[] DEFAULT_BUCKETS = - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES.stream() - .mapToDouble(d -> d) - .toArray(); + static final double[] NO_BUCKETS = new double[0]; protected abstract InstrumentationExtension testing(); @@ -65,7 +61,7 @@ void testMicrometerDistributionSummary() { .hasSum(7) .hasCount(3) .hasAttributes(attributeEntry("tag", "value")) - .hasBucketBoundaries(DEFAULT_BUCKETS))))); + .hasBucketBoundaries(NO_BUCKETS))))); testing() .waitAndAssertMetrics( INSTRUMENTATION_NAME, diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java index 50e126425597..de6f29b332c9 100644 --- a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java @@ -15,7 +15,6 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.metrics.data.HistogramPointData; -import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.assertj.core.api.AbstractIterableAssert; @@ -25,10 +24,7 @@ @SuppressWarnings("PreferJavaTimeOverload") public abstract class AbstractTimerTest { - static final double[] DEFAULT_BUCKETS = - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES.stream() - .mapToDouble(d -> d) - .toArray(); + static final double[] NO_BUCKETS = new double[0]; protected abstract InstrumentationExtension testing(); @@ -63,7 +59,7 @@ void testTimer() { .hasSum(42) .hasCount(1) .hasAttributes(attributeEntry("tag", "value")) - .hasBucketBoundaries(DEFAULT_BUCKETS))))); + .hasBucketBoundaries(NO_BUCKETS))))); testing() .waitAndAssertMetrics( INSTRUMENTATION_NAME, From 029e187281c19cc5a47f3a7b1e02985d0a45f94b Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Wed, 5 Jul 2023 19:10:05 +0200 Subject: [PATCH 53/95] Refactor the `FallbackNamePortGetter` in preparation for `client.port` (#8865) --- .../http/HttpClientAttributesExtractor.java | 8 +-- .../http/HttpCommonAttributesExtractor.java | 30 ++++------ .../http/HttpServerAttributesExtractor.java | 33 +++++------ .../net/NetClientAttributesExtractor.java | 6 +- .../net/NetServerAttributesExtractor.java | 8 +-- .../net/internal/FallbackNamePortGetter.java | 26 --------- .../InternalNetClientAttributesExtractor.java | 18 +++--- .../InternalNetServerAttributesExtractor.java | 16 ++++-- .../net/internal/NoopNamePortGetter.java | 24 -------- .../network/ClientAttributesExtractor.java | 4 +- .../network/ServerAttributesExtractor.java | 4 +- .../network/internal/AddressAndPort.java | 33 +++++++++++ .../FallbackAddressPortExtractor.java | 30 ++++++++++ .../InternalClientAttributesExtractor.java | 46 +++++++-------- .../InternalServerAttributesExtractor.java | 56 +++++++++---------- .../grizzly/GrizzlyNetAttributesGetter.java | 6 +- .../WebfluxServerNetAttributesGetter.java | 2 +- 17 files changed, 172 insertions(+), 178 deletions(-) delete mode 100644 instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java delete mode 100644 instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java create mode 100644 instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java create mode 100644 instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java index 816cc66fcd99..926d55e3a409 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java @@ -80,11 +80,11 @@ public static HttpClientAttributesExtractorBuilder capturedResponseHeaders, ToIntFunction resendCountIncrementer) { super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); - HttpNetNamePortGetter namePortGetter = - new HttpNetNamePortGetter<>(httpAttributesGetter); + HttpNetAddressPortExtractor addressPortExtractor = + new HttpNetAddressPortExtractor<>(httpAttributesGetter); internalNetExtractor = new InternalNetClientAttributesExtractor<>( - netAttributesGetter, namePortGetter, SemconvStability.emitOldHttpSemconv()); + netAttributesGetter, addressPortExtractor, SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( netAttributesGetter, @@ -95,7 +95,7 @@ public static HttpClientAttributesExtractorBuilder( netAttributesGetter, this::shouldCaptureServerPort, - namePortGetter, + addressPortExtractor, SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), InternalServerAttributesExtractor.Mode.PEER); diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java index 543dd81f5747..b9546cd71f5b 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java @@ -14,7 +14,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; @@ -140,42 +140,34 @@ private static Long parseNumber(@Nullable String number) { } } - static final class HttpNetNamePortGetter implements FallbackNamePortGetter { + static final class HttpNetAddressPortExtractor + implements FallbackAddressPortExtractor { private final HttpCommonAttributesGetter getter; - HttpNetNamePortGetter(HttpCommonAttributesGetter getter) { + HttpNetAddressPortExtractor(HttpCommonAttributesGetter getter) { this.getter = getter; } - @Nullable @Override - public String name(REQUEST request) { + public void extract(AddressPortSink sink, REQUEST request) { String host = firstHeaderValue(getter.getHttpRequestHeader(request, "host")); if (host == null) { - return null; + return; } - int hostHeaderSeparator = host.indexOf(':'); - return hostHeaderSeparator == -1 ? host : host.substring(0, hostHeaderSeparator); - } - @Nullable - @Override - public Integer port(REQUEST request) { - String host = firstHeaderValue(getter.getHttpRequestHeader(request, "host")); - if (host == null) { - return null; - } int hostHeaderSeparator = host.indexOf(':'); if (hostHeaderSeparator == -1) { - return null; + sink.setAddress(host); + return; } + + sink.setAddress(host.substring(0, hostHeaderSeparator)); try { - return Integer.parseInt(host.substring(hostHeaderSeparator + 1)); + sink.setPort(Integer.parseInt(host.substring(hostHeaderSeparator + 1))); } catch (NumberFormatException e) { logger.log(FINE, e.getMessage(), e); } - return null; } } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java index 154896c2fff0..9838cae2e44c 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java @@ -15,8 +15,8 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.InternalNetServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalNetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; @@ -95,8 +95,8 @@ public static HttpServerAttributesExtractorBuilder capturedResponseHeaders, Function httpRouteHolderGetter) { super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); - HttpNetNamePortGetter namePortGetter = - new HttpNetNamePortGetter<>(httpAttributesGetter); + HttpNetAddressPortExtractor addressPortExtractor = + new HttpNetAddressPortExtractor<>(httpAttributesGetter); internalUrlExtractor = new InternalUrlAttributesExtractor<>( httpAttributesGetter, @@ -105,7 +105,7 @@ public static HttpServerAttributesExtractorBuilder( - netAttributesGetter, namePortGetter, SemconvStability.emitOldHttpSemconv()); + netAttributesGetter, addressPortExtractor, SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( netAttributesGetter, @@ -116,14 +116,14 @@ public static HttpServerAttributesExtractorBuilder( netAttributesGetter, this::shouldCaptureServerPort, - namePortGetter, + addressPortExtractor, SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), InternalServerAttributesExtractor.Mode.HOST); internalClientExtractor = new InternalClientAttributesExtractor<>( netAttributesGetter, - new ClientAddressGetter<>(httpAttributesGetter), + new ClientAddressAndPortExtractor<>(httpAttributesGetter), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv()); this.httpRouteHolderGetter = httpRouteHolderGetter; @@ -204,41 +204,34 @@ public SpanKey internalGetSpanKey() { return SpanKey.HTTP_SERVER; } - private static final class ClientAddressGetter - implements FallbackNamePortGetter { + private static final class ClientAddressAndPortExtractor + implements FallbackAddressPortExtractor { private final HttpServerAttributesGetter getter; - private ClientAddressGetter(HttpServerAttributesGetter getter) { + private ClientAddressAndPortExtractor(HttpServerAttributesGetter getter) { this.getter = getter; } - @Nullable @Override - public String name(REQUEST request) { + public void extract(AddressPortSink sink, REQUEST request) { // try Forwarded String forwarded = firstHeaderValue(getter.getHttpRequestHeader(request, "forwarded")); if (forwarded != null) { forwarded = extractClientIpFromForwardedHeader(forwarded); if (forwarded != null) { - return forwarded; + sink.setAddress(forwarded); + return; } } // try X-Forwarded-For forwarded = firstHeaderValue(getter.getHttpRequestHeader(request, "x-forwarded-for")); if (forwarded != null) { - return extractClientIpFromForwardedForHeader(forwarded); + sink.setAddress(extractClientIpFromForwardedForHeader(forwarded)); } - return null; - } - - @Nullable - @Override - public Integer port(REQUEST request) { // TODO: client.port will be implemented in a future PR - return null; } } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java index 016ca5e24d79..d242794ebff4 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java @@ -8,8 +8,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.InternalNetClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalNetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkTransportFilter; @@ -39,7 +39,7 @@ public static AttributesExtractor create( private NetClientAttributesExtractor(NetClientAttributesGetter getter) { internalExtractor = new InternalNetClientAttributesExtractor<>( - getter, FallbackNamePortGetter.noop(), SemconvStability.emitOldHttpSemconv()); + getter, FallbackAddressPortExtractor.noop(), SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( getter, @@ -50,7 +50,7 @@ private NetClientAttributesExtractor(NetClientAttributesGetter( getter, (port, request) -> true, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), InternalServerAttributesExtractor.Mode.PEER); diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java index ddbfc8d1f32d..a06517a72735 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java @@ -8,8 +8,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.InternalNetServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalNetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; @@ -38,7 +38,7 @@ public static AttributesExtractor create( private NetServerAttributesExtractor(NetServerAttributesGetter getter) { internalExtractor = new InternalNetServerAttributesExtractor<>( - getter, FallbackNamePortGetter.noop(), SemconvStability.emitOldHttpSemconv()); + getter, FallbackAddressPortExtractor.noop(), SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( getter, @@ -49,14 +49,14 @@ private NetServerAttributesExtractor(NetServerAttributesGetter( getter, (port, request) -> true, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), InternalServerAttributesExtractor.Mode.HOST); internalClientExtractor = new InternalClientAttributesExtractor<>( getter, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv()); } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java deleted file mode 100644 index 93fc1d468eab..000000000000 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.instrumenter.net.internal; - -import javax.annotation.Nullable; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public interface FallbackNamePortGetter { - - @Nullable - String name(REQUEST request); - - @Nullable - Integer port(REQUEST request); - - @SuppressWarnings("unchecked") - static FallbackNamePortGetter noop() { - return (FallbackNamePortGetter) NoopNamePortGetter.INSTANCE; - } -} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java index 50c4e3649f67..ee2d6fc462c7 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java @@ -9,6 +9,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.AddressAndPort; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nullable; @@ -19,15 +21,15 @@ public final class InternalNetClientAttributesExtractor { private final NetClientAttributesGetter getter; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitOldHttpAttributes; public InternalNetClientAttributesExtractor( NetClientAttributesGetter getter, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitOldHttpAttributes) { this.getter = getter; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitOldHttpAttributes = emitOldHttpAttributes; } @@ -49,10 +51,12 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } private String extractPeerName(REQUEST request) { - String peerName = getter.getServerAddress(request); - if (peerName == null) { - peerName = fallbackNamePortGetter.name(request); + String serverAddress = getter.getServerAddress(request); + if (serverAddress != null) { + return serverAddress; } - return peerName; + AddressAndPort addressAndPort = new AddressAndPort(); + fallbackAddressPortExtractor.extract(addressAndPort, request); + return addressAndPort.getAddress(); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java index abd751fd008e..3feee1de5f16 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java @@ -9,6 +9,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.AddressAndPort; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; /** @@ -18,15 +20,15 @@ public final class InternalNetServerAttributesExtractor { private final NetServerAttributesGetter getter; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitOldHttpAttributes; public InternalNetServerAttributesExtractor( NetServerAttributesGetter getter, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitOldHttpAttributes) { this.getter = getter; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitOldHttpAttributes = emitOldHttpAttributes; } @@ -59,9 +61,11 @@ public void onStart(AttributesBuilder attributes, REQUEST request) { private String extractServerAddress(REQUEST request) { String serverAddress = getter.getServerAddress(request); - if (serverAddress == null) { - serverAddress = fallbackNamePortGetter.name(request); + if (serverAddress != null) { + return serverAddress; } - return serverAddress; + AddressAndPort addressAndPort = new AddressAndPort(); + fallbackAddressPortExtractor.extract(addressAndPort, request); + return addressAndPort.getAddress(); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java deleted file mode 100644 index 27b97b57a54f..000000000000 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.instrumenter.net.internal; - -import javax.annotation.Nullable; - -enum NoopNamePortGetter implements FallbackNamePortGetter { - INSTANCE; - - @Nullable - @Override - public String name(Object o) { - return null; - } - - @Nullable - @Override - public Integer port(Object o) { - return null; - } -} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java index 8c3bb259e8e4..9ca0ab202bef 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java @@ -8,7 +8,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalClientAttributesExtractor; import javax.annotation.Nullable; @@ -36,7 +36,7 @@ public static ClientAttributesExtractor c internalExtractor = new InternalClientAttributesExtractor<>( getter, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), /* emitStableUrlAttributes= */ true, /* emitOldHttpAttributes= */ false); } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java index c622da9adb64..956873d728aa 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java @@ -8,7 +8,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; import javax.annotation.Nullable; @@ -37,7 +37,7 @@ public static ServerAttributesExtractor c new InternalServerAttributesExtractor<>( getter, (port, request) -> true, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), /* emitStableUrlAttributes= */ true, /* emitOldHttpAttributes= */ false, // this param does not matter when old semconv is off diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java new file mode 100644 index 000000000000..803f9bc7d205 --- /dev/null +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network.internal; + +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AddressAndPort implements FallbackAddressPortExtractor.AddressPortSink { + + @Nullable String address; + @Nullable Integer port; + + @Override + public void setAddress(String address) { + this.address = address; + } + + @Override + public void setPort(int port) { + this.port = port; + } + + @Nullable + public String getAddress() { + return address; + } +} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java new file mode 100644 index 000000000000..cc83917d53bf --- /dev/null +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network.internal; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public interface FallbackAddressPortExtractor { + + void extract(AddressPortSink sink, REQUEST request); + + static FallbackAddressPortExtractor noop() { + return (sink, request) -> {}; + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + interface AddressPortSink { + + void setAddress(String address); + + void setPort(int port); + } +} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java index d71b18e205d6..3e0dfa163c73 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java @@ -8,7 +8,6 @@ import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.network.ClientAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nullable; @@ -20,41 +19,41 @@ public final class InternalClientAttributesExtractor { private final ClientAttributesGetter getter; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitStableUrlAttributes; private final boolean emitOldHttpAttributes; public InternalClientAttributesExtractor( ClientAttributesGetter getter, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitStableUrlAttributes, boolean emitOldHttpAttributes) { this.getter = getter; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitStableUrlAttributes = emitStableUrlAttributes; this.emitOldHttpAttributes = emitOldHttpAttributes; } public void onStart(AttributesBuilder attributes, REQUEST request) { - String clientAddress = extractClientAddress(request); + AddressAndPort clientAddressAndPort = extractClientAddressAndPort(request); + if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.CLIENT_ADDRESS, clientAddress); - Integer clientPort = extractClientPort(request); - if (clientPort != null && clientPort > 0) { - internalSet(attributes, NetworkAttributes.CLIENT_PORT, (long) clientPort); + internalSet(attributes, NetworkAttributes.CLIENT_ADDRESS, clientAddressAndPort.address); + if (clientAddressAndPort.port != null && clientAddressAndPort.port > 0) { + internalSet(attributes, NetworkAttributes.CLIENT_PORT, (long) clientAddressAndPort.port); } } if (emitOldHttpAttributes) { - internalSet(attributes, SemanticAttributes.HTTP_CLIENT_IP, clientAddress); + internalSet(attributes, SemanticAttributes.HTTP_CLIENT_IP, clientAddressAndPort.address); } } public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPONSE response) { - String clientAddress = extractClientAddress(request); + AddressAndPort clientAddressAndPort = extractClientAddressAndPort(request); String clientSocketAddress = getter.getClientSocketAddress(request, response); Integer clientSocketPort = getter.getClientSocketPort(request, response); - if (clientSocketAddress != null && !clientSocketAddress.equals(clientAddress)) { + if (clientSocketAddress != null && !clientSocketAddress.equals(clientAddressAndPort.address)) { if (emitStableUrlAttributes) { internalSet(attributes, NetworkAttributes.CLIENT_SOCKET_ADDRESS, clientSocketAddress); } @@ -64,8 +63,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } if (clientSocketPort != null && clientSocketPort > 0) { if (emitStableUrlAttributes) { - Integer clientPort = extractClientPort(request); - if (!clientSocketPort.equals(clientPort)) { + if (!clientSocketPort.equals(clientAddressAndPort.port)) { internalSet(attributes, NetworkAttributes.CLIENT_SOCKET_PORT, (long) clientSocketPort); } } @@ -75,19 +73,13 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } } - private String extractClientAddress(REQUEST request) { - String clientAddress = getter.getClientAddress(request); - if (clientAddress == null) { - clientAddress = fallbackNamePortGetter.name(request); - } - return clientAddress; - } - - private Integer extractClientPort(REQUEST request) { - Integer clientPort = getter.getClientPort(request); - if (clientPort == null) { - clientPort = fallbackNamePortGetter.port(request); + private AddressAndPort extractClientAddressAndPort(REQUEST request) { + AddressAndPort addressAndPort = new AddressAndPort(); + addressAndPort.address = getter.getClientAddress(request); + addressAndPort.port = getter.getClientPort(request); + if (addressAndPort.address == null && addressAndPort.port == null) { + fallbackAddressPortExtractor.extract(addressAndPort, request); } - return clientPort; + return addressAndPort; } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java index 3796801f9e61..6a3299c82b97 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java @@ -9,7 +9,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.network.ServerAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.function.BiPredicate; @@ -23,7 +22,7 @@ public final class InternalServerAttributesExtractor { private final ServerAttributesGetter getter; private final BiPredicate captureServerPortCondition; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitStableUrlAttributes; private final boolean emitOldHttpAttributes; private final Mode oldSemconvMode; @@ -31,45 +30,45 @@ public final class InternalServerAttributesExtractor { public InternalServerAttributesExtractor( ServerAttributesGetter getter, BiPredicate captureServerPortCondition, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitStableUrlAttributes, boolean emitOldHttpAttributes, Mode oldSemconvMode) { this.getter = getter; this.captureServerPortCondition = captureServerPortCondition; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitStableUrlAttributes = emitStableUrlAttributes; this.emitOldHttpAttributes = emitOldHttpAttributes; this.oldSemconvMode = oldSemconvMode; } public void onStart(AttributesBuilder attributes, REQUEST request) { - String serverAddress = extractServerAddress(request); + AddressAndPort serverAddressAndPort = extractServerAddressAndPort(request); + if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, serverAddress); + internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, serverAddressAndPort.address); } if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.address, serverAddress); + internalSet(attributes, oldSemconvMode.address, serverAddressAndPort.address); } - Integer serverPort = extractServerPort(request); - if (serverPort != null - && serverPort > 0 - && captureServerPortCondition.test(serverPort, request)) { + if (serverAddressAndPort.port != null + && serverAddressAndPort.port > 0 + && captureServerPortCondition.test(serverAddressAndPort.port, request)) { if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) serverPort); + internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) serverAddressAndPort.port); } if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.port, (long) serverPort); + internalSet(attributes, oldSemconvMode.port, (long) serverAddressAndPort.port); } } } public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPONSE response) { - String serverAddress = extractServerAddress(request); + AddressAndPort serverAddressAndPort = extractServerAddressAndPort(request); String serverSocketAddress = getter.getServerSocketAddress(request, response); - if (serverSocketAddress != null && !serverSocketAddress.equals(serverAddress)) { + if (serverSocketAddress != null && !serverSocketAddress.equals(serverAddressAndPort.address)) { if (emitStableUrlAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_ADDRESS, serverSocketAddress); } @@ -78,9 +77,10 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } } - Integer serverPort = extractServerPort(request); Integer serverSocketPort = getter.getServerSocketPort(request, response); - if (serverSocketPort != null && serverSocketPort > 0 && !serverSocketPort.equals(serverPort)) { + if (serverSocketPort != null + && serverSocketPort > 0 + && !serverSocketPort.equals(serverAddressAndPort.port)) { if (emitStableUrlAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); } @@ -90,7 +90,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } String serverSocketDomain = getter.getServerSocketDomain(request, response); - if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddress)) { + if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddressAndPort.address)) { if (emitStableUrlAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); } @@ -100,20 +100,14 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } } - private String extractServerAddress(REQUEST request) { - String serverAddress = getter.getServerAddress(request); - if (serverAddress == null) { - serverAddress = fallbackNamePortGetter.name(request); - } - return serverAddress; - } - - private Integer extractServerPort(REQUEST request) { - Integer serverPort = getter.getServerPort(request); - if (serverPort == null) { - serverPort = fallbackNamePortGetter.port(request); + private AddressAndPort extractServerAddressAndPort(REQUEST request) { + AddressAndPort addressAndPort = new AddressAndPort(); + addressAndPort.address = getter.getServerAddress(request); + addressAndPort.port = getter.getServerPort(request); + if (addressAndPort.address == null && addressAndPort.port == null) { + fallbackAddressPortExtractor.extract(addressAndPort, request); } - return serverPort; + return addressAndPort; } /** diff --git a/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java b/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java index 5190a8e0aa23..6d866e7ba152 100644 --- a/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java +++ b/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java @@ -69,12 +69,14 @@ public String getNetworkProtocolVersion( @Nullable @Override public String getServerAddress(HttpRequestPacket request) { - return request.getLocalHost(); + // rely on the 'host' header parsing + return null; } @Override public Integer getServerPort(HttpRequestPacket request) { - return request.getServerPort(); + // rely on the 'host' header parsing + return null; } @Nullable diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java index 5c3cfd395886..af6aab37a172 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java @@ -16,7 +16,7 @@ final class WebfluxServerNetAttributesGetter @Nullable @Override public String getServerAddress(ServerWebExchange request) { - return null; + return request.getRequest().getURI().getHost(); } @Nullable From aa4da0871480d3f4755aa13a8cf0e6f74f2f2173 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 5 Jul 2023 22:04:22 +0300 Subject: [PATCH 54/95] Free more disk space for smoke tests (#8874) Co-authored-by: Trask Stalnaker --- .github/workflows/build-common.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index a18217f11de6..43c86fb52f3d 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -285,6 +285,15 @@ jobs: run: git config --system core.longpaths true if: matrix.os == 'windows-latest' + # downloading the liberty image (a bit over 10gb) fails without freeing up more disk space on the runner + - name: Free disk space + run: | + df -h + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + df -h + if: matrix.os == 'ubuntu-latest' + - uses: actions/checkout@v3 - name: Set up JDK for running Gradle From 63d3c2c8835141405de58273a335346ad5d041d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 08:47:34 +0300 Subject: [PATCH 55/95] Bump ru.vyarus:gradle-animalsniffer-plugin from 1.7.0 to 1.7.1 (#8875) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- conventions/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index 720fe41c812f..d16028dee1dd 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("org.apache.httpcomponents:httpclient:4.5.14") implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") implementation("org.owasp:dependency-check-gradle:8.3.1") - implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.0") + implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.1") // When updating, also update dependencyManagement/build.gradle.kts implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.14.5") implementation("gradle.plugin.io.morethan.jmhreport:gradle-jmh-report:0.9.0") From 6b9bdfbfa2735a8c0a4a4d739eaad47cb25ad886 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 08:47:56 +0300 Subject: [PATCH 56/95] Bump io.quarkus.platform:quarkus-bom from 3.2.0.CR1 to 3.2.0.Final (#8876) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/images/quarkus/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/images/quarkus/build.gradle.kts b/smoke-tests/images/quarkus/build.gradle.kts index b27f4ed5e7a7..73c5143eaac3 100644 --- a/smoke-tests/images/quarkus/build.gradle.kts +++ b/smoke-tests/images/quarkus/build.gradle.kts @@ -16,7 +16,7 @@ plugins { } dependencies { - implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.2.0.CR1")) + implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.2.0.Final")) implementation("io.quarkus:quarkus-resteasy") } From 7144f5a504e33f3a830f40a6c8a563654bb71b91 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 6 Jul 2023 12:41:35 +0300 Subject: [PATCH 57/95] Exclude bad guava version from muzzle (#8883) --- instrumentation/guava-10.0/javaagent/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/guava-10.0/javaagent/build.gradle.kts b/instrumentation/guava-10.0/javaagent/build.gradle.kts index e4957cd26c75..5898b7e846a7 100644 --- a/instrumentation/guava-10.0/javaagent/build.gradle.kts +++ b/instrumentation/guava-10.0/javaagent/build.gradle.kts @@ -7,6 +7,7 @@ muzzle { group.set("com.google.guava") module.set("guava") versions.set("[10.0,]") + skip("32.1.0-android") assertInverse.set(true) } } From 6461f044e1b4668ff96978a6ba15814f27425935 Mon Sep 17 00:00:00 2001 From: Alexander Wert Date: Thu, 6 Jul 2023 12:08:59 +0200 Subject: [PATCH 58/95] Instrumentation for Elasticsearch 8+ (#8799) --- docs/supported-libraries.md | 4 +- instrumentation/elasticsearch/README.md | 7 + .../javaagent-unit-tests/build.gradle.kts | 8 + .../rest/ElasticsearchEndpointMapTest.java | 123 +++ .../javaagent/build.gradle.kts | 31 + .../apiclient/ApiClientInstrumentation.java | 55 ++ ...csearchApiClientInstrumentationModule.java | 25 + .../apiclient/ElasticsearchEndpointMap.java | 883 ++++++++++++++++++ .../test/java/ElasticsearchClientTest.java | 252 +++++ .../src/test/java/ElasticsearchRest5Test.java | 16 +- .../src/test/java/ElasticsearchRest6Test.java | 16 +- .../javaagent/build.gradle.kts | 1 + .../rest/v7_0/RestClientInstrumentation.java | 21 +- .../test/groovy/ElasticsearchRest7Test.groovy | 171 ---- .../src/test/java/ElasticsearchRest7Test.java | 211 +++++ ...ElasticsearchClientAttributeExtractor.java | 95 ++ .../rest/ElasticsearchDbAttributesGetter.java | 85 ++ .../rest/ElasticsearchEndpointDefinition.java | 177 ++++ .../ElasticsearchRestAttributesGetter.java | 49 - .../ElasticsearchRestInstrumenterFactory.java | 27 +- ...searchRestNetResponseAttributesGetter.java | 47 - .../rest/ElasticsearchRestRequest.java | 20 +- .../rest/ElasticsearchSpanNameExtractor.java | 23 + settings.gradle.kts | 2 + 24 files changed, 2050 insertions(+), 299 deletions(-) create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java create mode 100644 instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java delete mode 100644 instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy create mode 100644 instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java create mode 100644 instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java create mode 100644 instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java create mode 100644 instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java delete mode 100644 instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java delete mode 100644 instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java create mode 100644 instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 8a1bd07ba5f9..f5b06e5ecdcc 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -53,8 +53,9 @@ These are the supported libraries and frameworks: | [Eclipse Jetty HTTP Client](https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/client/HttpClient.html) | 9.2+ (not including 10+ yet) | [opentelemetry-jetty-httpclient-9.2](../instrumentation/jetty-httpclient/jetty-httpclient-9.2/library) | [HTTP Client Spans], [HTTP Client Metrics] | | [Eclipse Metro](https://projects.eclipse.org/projects/ee4j.metro) | 2.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] | | [Eclipse Mojarra](https://projects.eclipse.org/projects/ee4j.mojarra) | 1.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] | -| [Elasticsearch API](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans] | +| [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16+ and 8.0+ | N/A | [Elasticsearch Client Spans] | | [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html) | 5.0+ | N/A | [Database Client Spans] | +| [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans] | | [Finatra](https://github.com/twitter/finatra) | 2.9+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [Geode Client](https://geode.apache.org/) | 1.4+ | N/A | [Database Client Spans] | | [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | @@ -141,6 +142,7 @@ These are the supported libraries and frameworks: **[3]** Controller Spans are `INTERNAL` spans capturing the controller and/or view execution. See [Suppressing controller and/or view spans](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/#suppressing-controller-andor-view-spans). +[Elasticsearch Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/specification/database/elasticsearch.md [HTTP Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions [HTTP Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client [HTTP Server Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server diff --git a/instrumentation/elasticsearch/README.md b/instrumentation/elasticsearch/README.md index 0b78df613e25..18135dee857d 100644 --- a/instrumentation/elasticsearch/README.md +++ b/instrumentation/elasticsearch/README.md @@ -1,5 +1,12 @@ # Settings for the elasticsearch instrumentation +## Settings for the [Elasticsearch Java API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) instrumentation +| System property | Type | Default | Description | +|---|---|---|----------------------------------------------------------------------------------------------------------------------------| +| `otel.instrumentation.elasticsearch.capture-search-query` | `Boolean | `false` | Enable the capture of search query bodies. Attention: Elasticsearch queries may contain personal or sensitive information. | + + +## Settings for the [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) instrumentation | System property | Type | Default | Description | |---|---|---|---| | `otel.instrumentation.elasticsearch.experimental-span-attributes` | `Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts new file mode 100644 index 000000000000..2eb2dd8c5a71 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + testImplementation(project(":instrumentation:elasticsearch:elasticsearch-rest-common:javaagent")) + testImplementation(project(":instrumentation:elasticsearch:elasticsearch-api-client-7.16:javaagent")) +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java new file mode 100644 index 000000000000..1e639ac22638 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java @@ -0,0 +1,123 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient.ElasticsearchEndpointMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class ElasticsearchEndpointMapTest { + + private static final Set SEARCH_ENDPOINTS = + new HashSet<>( + Arrays.asList( + "search", + "async_search.submit", + "msearch", + "eql.search", + "terms_enum", + "search_template", + "msearch_template", + "render_search_template")); + + private static List getPathParts(String route) { + List pathParts = new ArrayList<>(); + String routeFragment = route; + int paramStartIndex = routeFragment.indexOf('{'); + while (paramStartIndex >= 0) { + int paramEndIndex = routeFragment.indexOf('}'); + if (paramEndIndex < 0 || paramEndIndex <= paramStartIndex + 1) { + throw new IllegalStateException("Invalid route syntax!"); + } + pathParts.add(routeFragment.substring(paramStartIndex + 1, paramEndIndex)); + + int nextIdx = paramEndIndex + 1; + if (nextIdx >= routeFragment.length()) { + break; + } + + routeFragment = routeFragment.substring(nextIdx); + paramStartIndex = routeFragment.indexOf('{'); + } + return pathParts; + } + + @Test + public void testIsSearchEndpoint() { + for (ElasticsearchEndpointDefinition esEndpointDefinition : + ElasticsearchEndpointMap.getAllEndpoints()) { + String endpointId = esEndpointDefinition.getEndpointName(); + assertEquals(SEARCH_ENDPOINTS.contains(endpointId), esEndpointDefinition.isSearchEndpoint()); + } + } + + @Test + public void testProcessPathParts() { + for (ElasticsearchEndpointDefinition esEndpointDefinition : + ElasticsearchEndpointMap.getAllEndpoints()) { + for (String route : + esEndpointDefinition.getRoutes().stream() + .map(ElasticsearchEndpointDefinition.Route::getName) + .collect(Collectors.toList())) { + List pathParts = getPathParts(route); + String resolvedRoute = route.replace("{", "").replace("}", ""); + Map observedParams = new HashMap<>(); + esEndpointDefinition.processPathParts(resolvedRoute, (k, v) -> observedParams.put(k, v)); + + Map expectedMap = new HashMap<>(); + pathParts.forEach(part -> expectedMap.put(part, part)); + + assertEquals(expectedMap, observedParams); + } + } + } + + @Test + public void testSearchEndpoint() { + ElasticsearchEndpointDefinition esEndpoint = ElasticsearchEndpointMap.get("search"); + Map observedParams = new HashMap<>(); + esEndpoint.processPathParts( + "/test-index-1,test-index-2/_search", (k, v) -> observedParams.put(k, v)); + + assertEquals("test-index-1,test-index-2", observedParams.get("index")); + } + + @Test + public void testBuildRegexPattern() { + Pattern pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_nodes/{node_id}/shutdown"); + assertEquals("^/_nodes/(?[^/]+)/shutdown$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_snapshot/{repository}/{snapshot}/_mount"); + assertEquals("^/_snapshot/(?[^/]+)/(?[^/]+)/_mount$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_security/profile/_suggest"); + assertEquals("^/_security/profile/_suggest$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_application/search_application/{name}"); + assertEquals("^/_application/search_application/(?[^/]+)$", pattern.pattern()); + + pattern = ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern("/"); + assertEquals("^/$", pattern.pattern()); + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts new file mode 100644 index 000000000000..01e2416df29d --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("co.elastic.clients") + module.set("elasticsearch-java") + versions.set("[7.16,)") + assertInverse.set(true) + } +} + +dependencies { + library("co.elastic.clients:elasticsearch-java:7.16.0") + + implementation(project(":instrumentation:elasticsearch:elasticsearch-rest-common:javaagent")) + + testInstrumentation(project(":instrumentation:elasticsearch:elasticsearch-rest-7.0:javaagent")) + testInstrumentation(project(":instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent")) + testInstrumentation(project(":instrumentation:apache-httpasyncclient-4.1:javaagent")) + + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.14.2") + testImplementation("org.testcontainers:elasticsearch") +} + +tasks { + test { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java new file mode 100644 index 000000000000..9388a92d0387 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import co.elastic.clients.transport.Endpoint; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchEndpointDefinition; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.elasticsearch.client.Request; + +public class ApiClientInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("co.elastic.clients.transport.rest_client.RestClientTransport"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("prepareLowLevelRequest")) + .and(takesArgument(1, named("co.elastic.clients.transport.Endpoint"))) + .and(returns(named("org.elasticsearch.client.Request"))), + this.getClass().getName() + "$RestClientTransportAdvice"); + } + + @SuppressWarnings("unused") + public static class RestClientTransportAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onPrepareLowLevelRequest( + @Advice.Argument(1) Endpoint endpoint, @Advice.Return Request request) { + VirtualField virtualField = + VirtualField.find(Request.class, ElasticsearchEndpointDefinition.class); + String endpointId = endpoint.id(); + if (endpointId.startsWith("es/") && endpointId.length() > 3) { + endpointId = endpointId.substring(3); + } + virtualField.set(request, ElasticsearchEndpointMap.get(endpointId)); + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java new file mode 100644 index 000000000000..b8e2cb866ba0 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient; + +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class ElasticsearchApiClientInstrumentationModule extends InstrumentationModule { + public ElasticsearchApiClientInstrumentationModule() { + super("elasticsearch-api-client-7.16", "elasticsearch"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ApiClientInstrumentation()); + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java new file mode 100644 index 000000000000..49096b7a5f97 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java @@ -0,0 +1,883 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient; + +import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchEndpointDefinition; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +public final class ElasticsearchEndpointMap { + + private static final Map routesMap; + + static { + Map routes = new HashMap<>(415); + initEndpoint(routes, "async_search.status", false, "/_async_search/status/{id}"); + initEndpoint(routes, "indices.analyze", false, "/_analyze", "/{index}/_analyze"); + initEndpoint(routes, "sql.clear_cursor", false, "/_sql/close"); + initEndpoint(routes, "ml.delete_datafeed", false, "/_ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "explain", false, "/{index}/_explain/{id}"); + initEndpoint( + routes, + "cat.thread_pool", + false, + "/_cat/thread_pool", + "/_cat/thread_pool/{thread_pool_patterns}"); + initEndpoint(routes, "ml.delete_calendar", false, "/_ml/calendars/{calendar_id}"); + initEndpoint(routes, "indices.create_data_stream", false, "/_data_stream/{name}"); + initEndpoint(routes, "cat.fielddata", false, "/_cat/fielddata", "/_cat/fielddata/{fields}"); + initEndpoint(routes, "security.enroll_node", false, "/_security/enroll/node"); + initEndpoint(routes, "slm.get_status", false, "/_slm/status"); + initEndpoint(routes, "ml.put_calendar", false, "/_ml/calendars/{calendar_id}"); + initEndpoint(routes, "create", false, "/{index}/_create/{id}"); + initEndpoint( + routes, + "ml.preview_datafeed", + false, + "/_ml/datafeeds/{datafeed_id}/_preview", + "/_ml/datafeeds/_preview"); + initEndpoint(routes, "indices.put_template", false, "/_template/{name}"); + initEndpoint( + routes, + "nodes.reload_secure_settings", + false, + "/_nodes/reload_secure_settings", + "/_nodes/{node_id}/reload_secure_settings"); + initEndpoint(routes, "indices.delete_data_stream", false, "/_data_stream/{name}"); + initEndpoint( + routes, + "transform.schedule_now_transform", + false, + "/_transform/{transform_id}/_schedule_now"); + initEndpoint(routes, "slm.stop", false, "/_slm/stop"); + initEndpoint(routes, "rollup.delete_job", false, "/_rollup/job/{id}"); + initEndpoint(routes, "cluster.put_component_template", false, "/_component_template/{name}"); + initEndpoint(routes, "delete_script", false, "/_scripts/{id}"); + initEndpoint(routes, "ml.delete_trained_model", false, "/_ml/trained_models/{model_id}"); + initEndpoint( + routes, + "indices.simulate_template", + false, + "/_index_template/_simulate", + "/_index_template/_simulate/{name}"); + initEndpoint(routes, "slm.get_lifecycle", false, "/_slm/policy/{policy_id}", "/_slm/policy"); + initEndpoint(routes, "security.enroll_kibana", false, "/_security/enroll/kibana"); + initEndpoint(routes, "fleet.search", false, "/{index}/_fleet/_fleet_search"); + initEndpoint(routes, "reindex_rethrottle", false, "/_reindex/{task_id}/_rethrottle"); + initEndpoint(routes, "ml.update_filter", false, "/_ml/filters/{filter_id}/_update"); + initEndpoint(routes, "rollup.get_rollup_caps", false, "/_rollup/data/{id}", "/_rollup/data"); + initEndpoint( + routes, "ccr.resume_auto_follow_pattern", false, "/_ccr/auto_follow/{name}/resume"); + initEndpoint(routes, "features.get_features", false, "/_features"); + initEndpoint(routes, "slm.get_stats", false, "/_slm/stats"); + initEndpoint(routes, "indices.clear_cache", false, "/_cache/clear", "/{index}/_cache/clear"); + initEndpoint( + routes, + "cluster.post_voting_config_exclusions", + false, + "/_cluster/voting_config_exclusions"); + initEndpoint(routes, "index", false, "/{index}/_doc/{id}", "/{index}/_doc"); + initEndpoint(routes, "cat.pending_tasks", false, "/_cat/pending_tasks"); + initEndpoint(routes, "indices.promote_data_stream", false, "/_data_stream/_promote/{name}"); + initEndpoint(routes, "ml.delete_filter", false, "/_ml/filters/{filter_id}"); + initEndpoint(routes, "sql.query", false, "/_sql"); + initEndpoint(routes, "ccr.follow_stats", false, "/{index}/_ccr/stats"); + initEndpoint(routes, "transform.stop_transform", false, "/_transform/{transform_id}/_stop"); + initEndpoint( + routes, + "security.has_privileges_user_profile", + false, + "/_security/profile/_has_privileges"); + initEndpoint( + routes, "autoscaling.delete_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "scripts_painless_execute", false, "/_scripts/painless/_execute"); + initEndpoint(routes, "indices.delete", false, "/{index}"); + initEndpoint( + routes, "security.clear_cached_roles", false, "/_security/role/{name}/_clear_cache"); + initEndpoint(routes, "eql.delete", false, "/_eql/search/{id}"); + initEndpoint(routes, "update", false, "/{index}/_update/{id}"); + initEndpoint( + routes, + "snapshot.clone", + false, + "/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}"); + initEndpoint(routes, "license.get_basic_status", false, "/_license/basic_status"); + initEndpoint(routes, "indices.close", false, "/{index}/_close"); + initEndpoint(routes, "security.saml_authenticate", false, "/_security/saml/authenticate"); + initEndpoint( + routes, "search_application.put", false, "/_application/search_application/{name}"); + initEndpoint(routes, "count", false, "/_count", "/{index}/_count"); + initEndpoint( + routes, + "migration.deprecations", + false, + "/_migration/deprecations", + "/{index}/_migration/deprecations"); + initEndpoint(routes, "indices.segments", false, "/_segments", "/{index}/_segments"); + initEndpoint(routes, "security.suggest_user_profiles", false, "/_security/profile/_suggest"); + initEndpoint(routes, "security.get_user_privileges", false, "/_security/user/_privileges"); + initEndpoint( + routes, + "indices.delete_alias", + false, + "/{index}/_alias/{name}", + "/{index}/_aliases/{name}"); + initEndpoint(routes, "indices.get_mapping", false, "/_mapping", "/{index}/_mapping"); + initEndpoint(routes, "indices.put_index_template", false, "/_index_template/{name}"); + initEndpoint( + routes, + "searchable_snapshots.stats", + false, + "/_searchable_snapshots/stats", + "/{index}/_searchable_snapshots/stats"); + initEndpoint(routes, "security.disable_user", false, "/_security/user/{username}/_disable"); + initEndpoint( + routes, + "ml.upgrade_job_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade"); + initEndpoint(routes, "delete", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "async_search.delete", false, "/_async_search/{id}"); + initEndpoint( + routes, "cat.transforms", false, "/_cat/transforms", "/_cat/transforms/{transform_id}"); + initEndpoint(routes, "ping", false, "/"); + initEndpoint(routes, "ccr.pause_auto_follow_pattern", false, "/_ccr/auto_follow/{name}/pause"); + initEndpoint(routes, "indices.shard_stores", false, "/_shard_stores", "/{index}/_shard_stores"); + initEndpoint( + routes, "ml.update_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_update"); + initEndpoint(routes, "logstash.delete_pipeline", false, "/_logstash/pipeline/{id}"); + initEndpoint(routes, "sql.translate", false, "/_sql/translate"); + initEndpoint(routes, "exists", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "snapshot.get_repository", false, "/_snapshot", "/_snapshot/{repository}"); + initEndpoint(routes, "snapshot.verify_repository", false, "/_snapshot/{repository}/_verify"); + initEndpoint(routes, "indices.put_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint(routes, "ml.open_job", false, "/_ml/anomaly_detectors/{job_id}/_open"); + initEndpoint( + routes, "security.update_user_profile_data", false, "/_security/profile/{uid}/_data"); + initEndpoint(routes, "enrich.put_policy", false, "/_enrich/policy/{name}"); + initEndpoint( + routes, + "ml.get_datafeed_stats", + false, + "/_ml/datafeeds/{datafeed_id}/_stats", + "/_ml/datafeeds/_stats"); + initEndpoint(routes, "open_point_in_time", false, "/{index}/_pit"); + initEndpoint(routes, "get_source", false, "/{index}/_source/{id}"); + initEndpoint(routes, "delete_by_query", false, "/{index}/_delete_by_query"); + initEndpoint(routes, "security.create_api_key", false, "/_security/api_key"); + initEndpoint(routes, "cat.tasks", false, "/_cat/tasks"); + initEndpoint(routes, "watcher.delete_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "ingest.processor_grok", false, "/_ingest/processor/grok"); + initEndpoint(routes, "ingest.put_pipeline", false, "/_ingest/pipeline/{id}"); + initEndpoint( + routes, + "ml.get_data_frame_analytics_stats", + false, + "/_ml/data_frame/analytics/_stats", + "/_ml/data_frame/analytics/{id}/_stats"); + initEndpoint( + routes, + "indices.data_streams_stats", + false, + "/_data_stream/_stats", + "/_data_stream/{name}/_stats"); + initEndpoint( + routes, "security.clear_cached_realms", false, "/_security/realm/{realms}/_clear_cache"); + initEndpoint(routes, "field_caps", false, "/_field_caps", "/{index}/_field_caps"); + initEndpoint(routes, "ml.evaluate_data_frame", false, "/_ml/data_frame/_evaluate"); + initEndpoint( + routes, + "ml.delete_forecast", + false, + "/_ml/anomaly_detectors/{job_id}/_forecast", + "/_ml/anomaly_detectors/{job_id}/_forecast/{forecast_id}"); + initEndpoint(routes, "enrich.get_policy", false, "/_enrich/policy/{name}", "/_enrich/policy"); + initEndpoint(routes, "rollup.start_job", false, "/_rollup/job/{id}/_start"); + initEndpoint(routes, "tasks.cancel", false, "/_tasks/_cancel", "/_tasks/{task_id}/_cancel"); + initEndpoint(routes, "security.saml_logout", false, "/_security/saml/logout"); + initEndpoint( + routes, "render_search_template", true, "/_render/template", "/_render/template/{id}"); + initEndpoint(routes, "ml.get_calendar_events", false, "/_ml/calendars/{calendar_id}/events"); + initEndpoint(routes, "security.enable_user_profile", false, "/_security/profile/{uid}/_enable"); + initEndpoint( + routes, "logstash.get_pipeline", false, "/_logstash/pipeline", "/_logstash/pipeline/{id}"); + initEndpoint(routes, "cat.snapshots", false, "/_cat/snapshots", "/_cat/snapshots/{repository}"); + initEndpoint(routes, "indices.add_block", false, "/{index}/_block/{block}"); + initEndpoint(routes, "terms_enum", true, "/{index}/_terms_enum"); + initEndpoint(routes, "ml.forecast", false, "/_ml/anomaly_detectors/{job_id}/_forecast"); + initEndpoint( + routes, "cluster.stats", false, "/_cluster/stats", "/_cluster/stats/nodes/{node_id}"); + initEndpoint(routes, "search_application.list", false, "/_application/search_application"); + initEndpoint(routes, "cat.count", false, "/_cat/count", "/_cat/count/{index}"); + initEndpoint(routes, "cat.segments", false, "/_cat/segments", "/_cat/segments/{index}"); + initEndpoint(routes, "ccr.resume_follow", false, "/{index}/_ccr/resume_follow"); + initEndpoint( + routes, "search_application.get", false, "/_application/search_application/{name}"); + initEndpoint( + routes, + "security.saml_service_provider_metadata", + false, + "/_security/saml/metadata/{realm_name}"); + initEndpoint(routes, "update_by_query", false, "/{index}/_update_by_query"); + initEndpoint(routes, "ml.stop_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_stop"); + initEndpoint(routes, "ilm.explain_lifecycle", false, "/{index}/_ilm/explain"); + initEndpoint( + routes, + "ml.put_trained_model_vocabulary", + false, + "/_ml/trained_models/{model_id}/vocabulary"); + initEndpoint(routes, "indices.exists", false, "/{index}"); + initEndpoint(routes, "ml.set_upgrade_mode", false, "/_ml/set_upgrade_mode"); + initEndpoint(routes, "security.saml_invalidate", false, "/_security/saml/invalidate"); + initEndpoint( + routes, + "ml.get_job_stats", + false, + "/_ml/anomaly_detectors/_stats", + "/_ml/anomaly_detectors/{job_id}/_stats"); + initEndpoint(routes, "cluster.allocation_explain", false, "/_cluster/allocation/explain"); + initEndpoint(routes, "watcher.activate_watch", false, "/_watcher/watch/{watch_id}/_activate"); + initEndpoint( + routes, + "searchable_snapshots.clear_cache", + false, + "/_searchable_snapshots/cache/clear", + "/{index}/_searchable_snapshots/cache/clear"); + initEndpoint( + routes, "msearch_template", true, "/_msearch/template", "/{index}/_msearch/template"); + initEndpoint(routes, "bulk", false, "/_bulk", "/{index}/_bulk"); + initEndpoint(routes, "cat.nodeattrs", false, "/_cat/nodeattrs"); + initEndpoint( + routes, "indices.get_index_template", false, "/_index_template", "/_index_template/{name}"); + initEndpoint(routes, "license.get", false, "/_license"); + initEndpoint(routes, "ccr.forget_follower", false, "/{index}/_ccr/forget_follower"); + initEndpoint(routes, "security.delete_role", false, "/_security/role/{name}"); + initEndpoint( + routes, "indices.validate_query", false, "/_validate/query", "/{index}/_validate/query"); + initEndpoint(routes, "tasks.get", false, "/_tasks/{task_id}"); + initEndpoint( + routes, "ml.start_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_start"); + initEndpoint(routes, "indices.create", false, "/{index}"); + initEndpoint( + routes, + "cluster.delete_voting_config_exclusions", + false, + "/_cluster/voting_config_exclusions"); + initEndpoint(routes, "info", false, "/"); + initEndpoint(routes, "watcher.stop", false, "/_watcher/_stop"); + initEndpoint(routes, "enrich.delete_policy", false, "/_enrich/policy/{name}"); + initEndpoint( + routes, + "cat.ml_data_frame_analytics", + false, + "/_cat/ml/data_frame/analytics", + "/_cat/ml/data_frame/analytics/{id}"); + initEndpoint( + routes, + "security.change_password", + false, + "/_security/user/{username}/_password", + "/_security/user/_password"); + initEndpoint(routes, "put_script", false, "/_scripts/{id}", "/_scripts/{id}/{context}"); + initEndpoint(routes, "ml.put_datafeed", false, "/_ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "cat.master", false, "/_cat/master"); + initEndpoint(routes, "features.reset_features", false, "/_features/_reset"); + initEndpoint(routes, "indices.get_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint( + routes, + "ml.get_data_frame_analytics", + false, + "/_ml/data_frame/analytics/{id}", + "/_ml/data_frame/analytics"); + initEndpoint( + routes, + "security.delete_service_token", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}"); + initEndpoint(routes, "indices.recovery", false, "/_recovery", "/{index}/_recovery"); + initEndpoint(routes, "cat.recovery", false, "/_cat/recovery", "/_cat/recovery/{index}"); + initEndpoint(routes, "indices.downsample", false, "/{index}/_downsample/{target_index}"); + initEndpoint(routes, "ingest.delete_pipeline", false, "/_ingest/pipeline/{id}"); + initEndpoint(routes, "async_search.get", false, "/_async_search/{id}"); + initEndpoint(routes, "eql.get", false, "/_eql/search/{id}"); + initEndpoint(routes, "cat.aliases", false, "/_cat/aliases", "/_cat/aliases/{name}"); + initEndpoint( + routes, + "security.get_service_credentials", + false, + "/_security/service/{namespace}/{service}/credential"); + initEndpoint(routes, "cat.allocation", false, "/_cat/allocation", "/_cat/allocation/{node_id}"); + initEndpoint( + routes, "ml.stop_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_stop"); + initEndpoint(routes, "indices.open", false, "/{index}/_open"); + initEndpoint(routes, "ilm.get_lifecycle", false, "/_ilm/policy/{policy}", "/_ilm/policy"); + initEndpoint(routes, "ilm.remove_policy", false, "/{index}/_ilm/remove"); + initEndpoint( + routes, + "security.get_role_mapping", + false, + "/_security/role_mapping/{name}", + "/_security/role_mapping"); + initEndpoint(routes, "snapshot.create", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "watcher.get_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "license.post_start_trial", false, "/_license/start_trial"); + initEndpoint(routes, "snapshot.restore", false, "/_snapshot/{repository}/{snapshot}/_restore"); + initEndpoint(routes, "indices.put_mapping", false, "/{index}/_mapping"); + initEndpoint( + routes, "ml.delete_calendar_job", false, "/_ml/calendars/{calendar_id}/jobs/{job_id}"); + initEndpoint( + routes, "security.clear_api_key_cache", false, "/_security/api_key/{ids}/_clear_cache"); + initEndpoint(routes, "slm.start", false, "/_slm/start"); + initEndpoint( + routes, + "cat.component_templates", + false, + "/_cat/component_templates", + "/_cat/component_templates/{name}"); + initEndpoint(routes, "security.enable_user", false, "/_security/user/{username}/_enable"); + initEndpoint(routes, "cluster.delete_component_template", false, "/_component_template/{name}"); + initEndpoint(routes, "security.get_role", false, "/_security/role/{name}", "/_security/role"); + initEndpoint( + routes, "ingest.get_pipeline", false, "/_ingest/pipeline", "/_ingest/pipeline/{id}"); + initEndpoint( + routes, + "ml.delete_expired_data", + false, + "/_ml/_delete_expired_data/{job_id}", + "/_ml/_delete_expired_data"); + initEndpoint( + routes, + "indices.get_settings", + false, + "/_settings", + "/{index}/_settings", + "/{index}/_settings/{name}", + "/_settings/{name}"); + initEndpoint(routes, "ccr.follow", false, "/{index}/_ccr/follow"); + initEndpoint( + routes, "termvectors", false, "/{index}/_termvectors/{id}", "/{index}/_termvectors"); + initEndpoint(routes, "ml.post_data", false, "/_ml/anomaly_detectors/{job_id}/_data"); + initEndpoint(routes, "eql.search", true, "/{index}/_eql/search"); + initEndpoint( + routes, + "ml.get_trained_models", + false, + "/_ml/trained_models/{model_id}", + "/_ml/trained_models"); + initEndpoint( + routes, "security.disable_user_profile", false, "/_security/profile/{uid}/_disable"); + initEndpoint(routes, "security.put_privileges", false, "/_security/privilege"); + initEndpoint(routes, "cat.nodes", false, "/_cat/nodes"); + initEndpoint( + routes, "nodes.info", false, "/_nodes", "/_nodes/{node_id}", "/_nodes/{node_id}/{metric}"); + initEndpoint(routes, "graph.explore", false, "/{index}/_graph/explore"); + initEndpoint( + routes, "autoscaling.put_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "cat.templates", false, "/_cat/templates", "/_cat/templates/{name}"); + initEndpoint(routes, "cluster.remote_info", false, "/_remote/info"); + initEndpoint(routes, "rank_eval", false, "/_rank_eval", "/{index}/_rank_eval"); + initEndpoint( + routes, "security.delete_privileges", false, "/_security/privilege/{application}/{name}"); + initEndpoint( + routes, + "security.get_privileges", + false, + "/_security/privilege", + "/_security/privilege/{application}", + "/_security/privilege/{application}/{name}"); + initEndpoint(routes, "scroll", false, "/_search/scroll"); + initEndpoint(routes, "license.delete", false, "/_license"); + initEndpoint(routes, "indices.disk_usage", false, "/{index}/_disk_usage"); + initEndpoint(routes, "msearch", true, "/_msearch", "/{index}/_msearch"); + initEndpoint(routes, "indices.field_usage_stats", false, "/{index}/_field_usage_stats"); + initEndpoint( + routes, "indices.rollover", false, "/{alias}/_rollover", "/{alias}/_rollover/{new_index}"); + initEndpoint( + routes, + "cat.ml_trained_models", + false, + "/_cat/ml/trained_models", + "/_cat/ml/trained_models/{model_id}"); + initEndpoint( + routes, + "ml.delete_trained_model_alias", + false, + "/_ml/trained_models/{model_id}/model_aliases/{model_alias}"); + initEndpoint(routes, "indices.get", false, "/{index}"); + initEndpoint(routes, "sql.get_async_status", false, "/_sql/async/status/{id}"); + initEndpoint(routes, "ilm.stop", false, "/_ilm/stop"); + initEndpoint(routes, "security.put_user", false, "/_security/user/{username}"); + initEndpoint( + routes, + "cluster.state", + false, + "/_cluster/state", + "/_cluster/state/{metric}", + "/_cluster/state/{metric}/{index}"); + initEndpoint(routes, "indices.put_settings", false, "/_settings", "/{index}/_settings"); + initEndpoint(routes, "knn_search", false, "/{index}/_knn_search"); + initEndpoint(routes, "get", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "eql.get_status", false, "/_eql/search/status/{id}"); + initEndpoint(routes, "ssl.certificates", false, "/_ssl/certificates"); + initEndpoint( + routes, + "ml.get_model_snapshots", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", + "/_ml/anomaly_detectors/{job_id}/model_snapshots"); + initEndpoint( + routes, + "nodes.clear_repositories_metering_archive", + false, + "/_nodes/{node_id}/_repositories_metering/{max_archive_version}"); + initEndpoint(routes, "security.put_role", false, "/_security/role/{name}"); + initEndpoint( + routes, "ml.get_influencers", false, "/_ml/anomaly_detectors/{job_id}/results/influencers"); + initEndpoint(routes, "transform.upgrade_transforms", false, "/_transform/_upgrade"); + initEndpoint( + routes, + "ml.delete_calendar_event", + false, + "/_ml/calendars/{calendar_id}/events/{event_id}"); + initEndpoint( + routes, + "indices.get_field_mapping", + false, + "/_mapping/field/{fields}", + "/{index}/_mapping/field/{fields}"); + initEndpoint( + routes, + "transform.preview_transform", + false, + "/_transform/{transform_id}/_preview", + "/_transform/_preview"); + initEndpoint(routes, "tasks.list", false, "/_tasks"); + initEndpoint( + routes, + "ml.clear_trained_model_deployment_cache", + false, + "/_ml/trained_models/{model_id}/deployment/cache/_clear"); + initEndpoint(routes, "cluster.reroute", false, "/_cluster/reroute"); + initEndpoint(routes, "security.saml_complete_logout", false, "/_security/saml/complete_logout"); + initEndpoint( + routes, + "indices.simulate_index_template", + false, + "/_index_template/_simulate_index/{name}"); + initEndpoint(routes, "snapshot.get", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "ccr.put_auto_follow_pattern", false, "/_ccr/auto_follow/{name}"); + initEndpoint( + routes, "nodes.hot_threads", false, "/_nodes/hot_threads", "/_nodes/{node_id}/hot_threads"); + initEndpoint( + routes, + "ml.preview_data_frame_analytics", + false, + "/_ml/data_frame/analytics/_preview", + "/_ml/data_frame/analytics/{id}/_preview"); + initEndpoint(routes, "indices.flush", false, "/_flush", "/{index}/_flush"); + initEndpoint(routes, "cluster.exists_component_template", false, "/_component_template/{name}"); + initEndpoint( + routes, + "snapshot.status", + false, + "/_snapshot/_status", + "/_snapshot/{repository}/_status", + "/_snapshot/{repository}/{snapshot}/_status"); + initEndpoint(routes, "ml.update_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_update"); + initEndpoint(routes, "indices.update_aliases", false, "/_aliases"); + initEndpoint(routes, "autoscaling.get_autoscaling_capacity", false, "/_autoscaling/capacity"); + initEndpoint(routes, "migration.post_feature_upgrade", false, "/_migration/system_features"); + initEndpoint( + routes, "ml.get_records", false, "/_ml/anomaly_detectors/{job_id}/results/records"); + initEndpoint( + routes, + "indices.get_alias", + false, + "/_alias", + "/_alias/{name}", + "/{index}/_alias/{name}", + "/{index}/_alias"); + initEndpoint(routes, "logstash.put_pipeline", false, "/_logstash/pipeline/{id}"); + initEndpoint(routes, "snapshot.delete_repository", false, "/_snapshot/{repository}"); + initEndpoint( + routes, + "security.has_privileges", + false, + "/_security/user/_has_privileges", + "/_security/user/{user}/_has_privileges"); + initEndpoint(routes, "cat.indices", false, "/_cat/indices", "/_cat/indices/{index}"); + initEndpoint( + routes, + "ccr.get_auto_follow_pattern", + false, + "/_ccr/auto_follow", + "/_ccr/auto_follow/{name}"); + initEndpoint(routes, "ml.start_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_start"); + initEndpoint(routes, "indices.clone", false, "/{index}/_clone/{target}"); + initEndpoint( + routes, "search_application.delete", false, "/_application/search_application/{name}"); + initEndpoint(routes, "security.query_api_keys", false, "/_security/_query/api_key"); + initEndpoint(routes, "ml.flush_job", false, "/_ml/anomaly_detectors/{job_id}/_flush"); + initEndpoint( + routes, + "security.clear_cached_privileges", + false, + "/_security/privilege/{application}/_clear_cache"); + initEndpoint(routes, "indices.exists_index_template", false, "/_index_template/{name}"); + initEndpoint(routes, "indices.explain_data_lifecycle", false, "/{index}/_lifecycle/explain"); + initEndpoint( + routes, "indices.put_alias", false, "/{index}/_alias/{name}", "/{index}/_aliases/{name}"); + initEndpoint( + routes, + "ml.get_buckets", + false, + "/_ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", + "/_ml/anomaly_detectors/{job_id}/results/buckets"); + initEndpoint( + routes, + "ml.put_trained_model_definition_part", + false, + "/_ml/trained_models/{model_id}/definition/{part}"); + initEndpoint(routes, "get_script", false, "/_scripts/{id}"); + initEndpoint( + routes, + "ingest.simulate", + false, + "/_ingest/pipeline/_simulate", + "/_ingest/pipeline/{id}/_simulate"); + initEndpoint(routes, "indices.migrate_to_data_stream", false, "/_data_stream/_migrate/{name}"); + initEndpoint(routes, "enrich.execute_policy", false, "/_enrich/policy/{name}/_execute"); + initEndpoint(routes, "indices.split", false, "/{index}/_split/{target}"); + initEndpoint( + routes, + "ml.delete_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}"); + initEndpoint( + routes, + "nodes.usage", + false, + "/_nodes/usage", + "/_nodes/{node_id}/usage", + "/_nodes/usage/{metric}", + "/_nodes/{node_id}/usage/{metric}"); + initEndpoint(routes, "cat.help", false, "/_cat"); + initEndpoint( + routes, "ml.estimate_model_memory", false, "/_ml/anomaly_detectors/_estimate_model_memory"); + initEndpoint(routes, "exists_source", false, "/{index}/_source/{id}"); + initEndpoint(routes, "ml.put_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}"); + initEndpoint(routes, "security.put_role_mapping", false, "/_security/role_mapping/{name}"); + initEndpoint(routes, "rollup.get_rollup_index_caps", false, "/{index}/_rollup/data"); + initEndpoint(routes, "transform.reset_transform", false, "/_transform/{transform_id}/_reset"); + initEndpoint( + routes, + "ml.infer_trained_model", + false, + "/_ml/trained_models/{model_id}/_infer", + "/_ml/trained_models/{model_id}/deployment/_infer"); + initEndpoint(routes, "reindex", false, "/_reindex"); + initEndpoint(routes, "ml.put_trained_model", false, "/_ml/trained_models/{model_id}"); + initEndpoint( + routes, + "cat.ml_jobs", + false, + "/_cat/ml/anomaly_detectors", + "/_cat/ml/anomaly_detectors/{job_id}"); + initEndpoint( + routes, + "search_application.search", + false, + "/_application/search_application/{name}/_search"); + initEndpoint(routes, "ilm.put_lifecycle", false, "/_ilm/policy/{policy}"); + initEndpoint(routes, "security.get_token", false, "/_security/oauth2/token"); + initEndpoint(routes, "ilm.move_to_step", false, "/_ilm/move/{index}"); + initEndpoint(routes, "search_template", true, "/_search/template", "/{index}/_search/template"); + initEndpoint(routes, "indices.delete_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint(routes, "indices.get_data_stream", false, "/_data_stream", "/_data_stream/{name}"); + initEndpoint(routes, "ml.get_filters", false, "/_ml/filters", "/_ml/filters/{filter_id}"); + initEndpoint( + routes, + "cat.ml_datafeeds", + false, + "/_cat/ml/datafeeds", + "/_cat/ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "rollup.rollup_search", false, "/{index}/_rollup_search"); + initEndpoint(routes, "ml.put_job", false, "/_ml/anomaly_detectors/{job_id}"); + initEndpoint( + routes, "update_by_query_rethrottle", false, "/_update_by_query/{task_id}/_rethrottle"); + initEndpoint(routes, "indices.delete_index_template", false, "/_index_template/{name}"); + initEndpoint( + routes, "indices.reload_search_analyzers", false, "/{index}/_reload_search_analyzers"); + initEndpoint(routes, "cluster.get_settings", false, "/_cluster/settings"); + initEndpoint(routes, "cluster.put_settings", false, "/_cluster/settings"); + initEndpoint(routes, "transform.put_transform", false, "/_transform/{transform_id}"); + initEndpoint(routes, "watcher.stats", false, "/_watcher/stats", "/_watcher/stats/{metric}"); + initEndpoint(routes, "ccr.delete_auto_follow_pattern", false, "/_ccr/auto_follow/{name}"); + initEndpoint(routes, "mtermvectors", false, "/_mtermvectors", "/{index}/_mtermvectors"); + initEndpoint(routes, "license.post", false, "/_license"); + initEndpoint(routes, "xpack.info", false, "/_xpack"); + initEndpoint( + routes, "dangling_indices.import_dangling_index", false, "/_dangling/{index_uuid}"); + initEndpoint( + routes, + "nodes.get_repositories_metering_info", + false, + "/_nodes/{node_id}/_repositories_metering"); + initEndpoint( + routes, "transform.get_transform_stats", false, "/_transform/{transform_id}/_stats"); + initEndpoint(routes, "mget", false, "/_mget", "/{index}/_mget"); + initEndpoint(routes, "security.get_builtin_privileges", false, "/_security/privilege/_builtin"); + initEndpoint( + routes, + "ml.update_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update"); + initEndpoint(routes, "ml.info", false, "/_ml/info"); + initEndpoint(routes, "indices.exists_template", false, "/_template/{name}"); + initEndpoint( + routes, + "watcher.ack_watch", + false, + "/_watcher/watch/{watch_id}/_ack", + "/_watcher/watch/{watch_id}/_ack/{action_id}"); + initEndpoint( + routes, "security.get_user", false, "/_security/user/{username}", "/_security/user"); + initEndpoint( + routes, "shutdown.get_node", false, "/_nodes/shutdown", "/_nodes/{node_id}/shutdown"); + initEndpoint(routes, "watcher.start", false, "/_watcher/_start"); + initEndpoint(routes, "indices.shrink", false, "/{index}/_shrink/{target}"); + initEndpoint(routes, "license.post_start_basic", false, "/_license/start_basic"); + initEndpoint(routes, "xpack.usage", false, "/_xpack/usage"); + initEndpoint(routes, "ilm.delete_lifecycle", false, "/_ilm/policy/{policy}"); + initEndpoint(routes, "ccr.follow_info", false, "/{index}/_ccr/info"); + initEndpoint( + routes, "ml.put_calendar_job", false, "/_ml/calendars/{calendar_id}/jobs/{job_id}"); + initEndpoint(routes, "rollup.put_job", false, "/_rollup/job/{id}"); + initEndpoint(routes, "clear_scroll", false, "/_search/scroll"); + initEndpoint(routes, "ml.delete_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}"); + initEndpoint(routes, "security.get_api_key", false, "/_security/api_key"); + initEndpoint(routes, "cat.health", false, "/_cat/health"); + initEndpoint(routes, "security.invalidate_token", false, "/_security/oauth2/token"); + initEndpoint(routes, "slm.delete_lifecycle", false, "/_slm/policy/{policy_id}"); + initEndpoint( + routes, + "ml.stop_trained_model_deployment", + false, + "/_ml/trained_models/{model_id}/deployment/_stop"); + initEndpoint(routes, "monitoring.bulk", false, "/_monitoring/bulk", "/_monitoring/{type}/bulk"); + initEndpoint( + routes, + "indices.stats", + false, + "/_stats", + "/_stats/{metric}", + "/{index}/_stats", + "/{index}/_stats/{metric}"); + initEndpoint( + routes, + "searchable_snapshots.cache_stats", + false, + "/_searchable_snapshots/cache/stats", + "/_searchable_snapshots/{node_id}/cache/stats"); + initEndpoint(routes, "async_search.submit", true, "/_async_search", "/{index}/_async_search"); + initEndpoint(routes, "rollup.get_jobs", false, "/_rollup/job/{id}", "/_rollup/job"); + initEndpoint( + routes, + "ml.revert_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert"); + initEndpoint(routes, "transform.delete_transform", false, "/_transform/{transform_id}"); + initEndpoint(routes, "cluster.pending_tasks", false, "/_cluster/pending_tasks"); + initEndpoint( + routes, + "ml.get_model_snapshot_upgrade_stats", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade/_stats"); + initEndpoint( + routes, + "ml.get_categories", + false, + "/_ml/anomaly_detectors/{job_id}/results/categories/{category_id}", + "/_ml/anomaly_detectors/{job_id}/results/categories"); + initEndpoint(routes, "ccr.pause_follow", false, "/{index}/_ccr/pause_follow"); + initEndpoint(routes, "security.authenticate", false, "/_security/_authenticate"); + initEndpoint(routes, "enrich.stats", false, "/_enrich/_stats"); + initEndpoint( + routes, + "ml.put_trained_model_alias", + false, + "/_ml/trained_models/{model_id}/model_aliases/{model_alias}"); + initEndpoint( + routes, + "ml.get_overall_buckets", + false, + "/_ml/anomaly_detectors/{job_id}/results/overall_buckets"); + initEndpoint(routes, "indices.get_template", false, "/_template", "/_template/{name}"); + initEndpoint(routes, "security.delete_role_mapping", false, "/_security/role_mapping/{name}"); + initEndpoint( + routes, "ml.get_datafeeds", false, "/_ml/datafeeds/{datafeed_id}", "/_ml/datafeeds"); + initEndpoint(routes, "slm.execute_lifecycle", false, "/_slm/policy/{policy_id}/_execute"); + initEndpoint(routes, "close_point_in_time", false, "/_pit"); + initEndpoint(routes, "snapshot.cleanup_repository", false, "/_snapshot/{repository}/_cleanup"); + initEndpoint( + routes, "autoscaling.get_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "slm.put_lifecycle", false, "/_slm/policy/{policy_id}"); + initEndpoint( + routes, "ml.get_jobs", false, "/_ml/anomaly_detectors/{job_id}", "/_ml/anomaly_detectors"); + initEndpoint( + routes, + "ml.get_trained_models_stats", + false, + "/_ml/trained_models/{model_id}/_stats", + "/_ml/trained_models/_stats"); + initEndpoint( + routes, "ml.validate_detector", false, "/_ml/anomaly_detectors/_validate/detector"); + initEndpoint(routes, "watcher.put_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "transform.update_transform", false, "/_transform/{transform_id}/_update"); + initEndpoint(routes, "ml.post_calendar_events", false, "/_ml/calendars/{calendar_id}/events"); + initEndpoint( + routes, "migration.get_feature_upgrade_status", false, "/_migration/system_features"); + initEndpoint(routes, "get_script_context", false, "/_script_context"); + initEndpoint(routes, "ml.put_filter", false, "/_ml/filters/{filter_id}"); + initEndpoint(routes, "ml.update_job", false, "/_ml/anomaly_detectors/{job_id}/_update"); + initEndpoint(routes, "ingest.geo_ip_stats", false, "/_ingest/geoip/stats"); + initEndpoint(routes, "security.delete_user", false, "/_security/user/{username}"); + initEndpoint(routes, "indices.unfreeze", false, "/{index}/_unfreeze"); + initEndpoint(routes, "snapshot.create_repository", false, "/_snapshot/{repository}"); + initEndpoint( + routes, + "cluster.get_component_template", + false, + "/_component_template", + "/_component_template/{name}"); + initEndpoint(routes, "ilm.migrate_to_data_tiers", false, "/_ilm/migrate_to_data_tiers"); + initEndpoint(routes, "indices.refresh", false, "/_refresh", "/{index}/_refresh"); + initEndpoint( + routes, "ml.get_calendars", false, "/_ml/calendars", "/_ml/calendars/{calendar_id}"); + initEndpoint( + routes, "watcher.deactivate_watch", false, "/_watcher/watch/{watch_id}/_deactivate"); + initEndpoint(routes, "cluster.health", false, "/_cluster/health", "/_cluster/health/{index}"); + initEndpoint( + routes, "dangling_indices.delete_dangling_index", false, "/_dangling/{index_uuid}"); + initEndpoint(routes, "health_report", false, "/_health_report", "/_health_report/{feature}"); + initEndpoint(routes, "watcher.query_watches", false, "/_watcher/_query/watches"); + initEndpoint(routes, "ccr.unfollow", false, "/{index}/_ccr/unfollow"); + initEndpoint(routes, "ml.validate", false, "/_ml/anomaly_detectors/_validate"); + initEndpoint(routes, "cat.plugins", false, "/_cat/plugins"); + initEndpoint( + routes, + "watcher.execute_watch", + false, + "/_watcher/watch/{id}/_execute", + "/_watcher/watch/_execute"); + initEndpoint(routes, "search_shards", false, "/_search_shards", "/{index}/_search_shards"); + initEndpoint(routes, "cat.shards", false, "/_cat/shards", "/_cat/shards/{index}"); + initEndpoint(routes, "ml.delete_job", false, "/_ml/anomaly_detectors/{job_id}"); + initEndpoint(routes, "ilm.start", false, "/_ilm/start"); + initEndpoint(routes, "security.get_user_profile", false, "/_security/profile/{uid}"); + initEndpoint(routes, "indices.modify_data_stream", false, "/_data_stream/_modify"); + initEndpoint(routes, "indices.exists_alias", false, "/_alias/{name}", "/{index}/_alias/{name}"); + initEndpoint(routes, "rollup.stop_job", false, "/_rollup/job/{id}/_stop"); + initEndpoint(routes, "dangling_indices.list_dangling_indices", false, "/_dangling"); + initEndpoint(routes, "snapshot.delete", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "security.activate_user_profile", false, "/_security/profile/_activate"); + initEndpoint( + routes, + "ml.start_trained_model_deployment", + false, + "/_ml/trained_models/{model_id}/deployment/_start"); + initEndpoint(routes, "transform.start_transform", false, "/_transform/{transform_id}/_start"); + initEndpoint(routes, "cat.repositories", false, "/_cat/repositories"); + initEndpoint(routes, "ilm.get_status", false, "/_ilm/status"); + initEndpoint(routes, "shutdown.delete_node", false, "/_nodes/{node_id}/shutdown"); + initEndpoint( + routes, + "nodes.stats", + false, + "/_nodes/stats", + "/_nodes/{node_id}/stats", + "/_nodes/stats/{metric}", + "/_nodes/{node_id}/stats/{metric}", + "/_nodes/stats/{metric}/{index_metric}", + "/_nodes/{node_id}/stats/{metric}/{index_metric}"); + initEndpoint(routes, "get_script_languages", false, "/_script_language"); + initEndpoint(routes, "slm.execute_retention", false, "/_slm/_execute_retention"); + initEndpoint( + routes, + "security.get_service_accounts", + false, + "/_security/service/{namespace}/{service}", + "/_security/service/{namespace}", + "/_security/service"); + initEndpoint(routes, "shutdown.put_node", false, "/_nodes/{node_id}/shutdown"); + initEndpoint(routes, "indices.resolve_index", false, "/_resolve/index/{name}"); + initEndpoint(routes, "search", true, "/_search", "/{index}/_search"); + initEndpoint(routes, "sql.get_async", false, "/_sql/async/{id}"); + initEndpoint( + routes, "delete_by_query_rethrottle", false, "/_delete_by_query/{task_id}/_rethrottle"); + initEndpoint( + routes, "transform.get_transform", false, "/_transform/{transform_id}", "/_transform"); + initEndpoint(routes, "security.invalidate_api_key", false, "/_security/api_key"); + initEndpoint(routes, "security.saml_prepare_authentication", false, "/_security/saml/prepare"); + initEndpoint( + routes, "ml.get_memory_stats", false, "/_ml/memory/_stats", "/_ml/memory/{node_id}/_stats"); + initEndpoint(routes, "ccr.stats", false, "/_ccr/stats"); + initEndpoint(routes, "indices.forcemerge", false, "/_forcemerge", "/{index}/_forcemerge"); + initEndpoint(routes, "indices.delete_template", false, "/_template/{name}"); + initEndpoint(routes, "sql.delete_async", false, "/_sql/async/delete/{id}"); + initEndpoint(routes, "security.update_api_key", false, "/_security/api_key/{id}"); + initEndpoint( + routes, + "security.create_service_token", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}", + "/_security/service/{namespace}/{service}/credential/token"); + initEndpoint(routes, "license.get_trial_status", false, "/_license/trial_status"); + initEndpoint( + routes, "searchable_snapshots.mount", false, "/_snapshot/{repository}/{snapshot}/_mount"); + initEndpoint(routes, "security.grant_api_key", false, "/_security/api_key/grant"); + initEndpoint(routes, "ilm.retry", false, "/{index}/_ilm/retry"); + initEndpoint(routes, "ml.reset_job", false, "/_ml/anomaly_detectors/{job_id}/_reset"); + initEndpoint(routes, "ml.close_job", false, "/_ml/anomaly_detectors/{job_id}/_close"); + initEndpoint( + routes, + "ml.explain_data_frame_analytics", + false, + "/_ml/data_frame/analytics/_explain", + "/_ml/data_frame/analytics/{id}/_explain"); + initEndpoint( + routes, + "security.clear_cached_service_tokens", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}/_clear_cache"); + initEndpoint(routes, "search_mvt", false, "/{index}/_mvt/{field}/{zoom}/{x}/{y}"); + routesMap = Collections.unmodifiableMap(routes); + } + + private ElasticsearchEndpointMap() {} + + private static void initEndpoint( + Map map, + String endpointId, + boolean isSearchEndpoint, + String... routes) { + ElasticsearchEndpointDefinition endpointDef = + new ElasticsearchEndpointDefinition(endpointId, routes, isSearchEndpoint); + map.put(endpointId, endpointDef); + } + + @Nullable + public static ElasticsearchEndpointDefinition get(String endpointId) { + return routesMap.get(endpointId); + } + + public static Collection getAllEndpoints() { + return routesMap.values(); + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java new file mode 100644 index 000000000000..19ce31686b77 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java @@ -0,0 +1,252 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.InfoResponse; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.Version; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.http.HttpHost; +import org.assertj.core.api.AbstractLongAssert; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +class ElasticsearchClientTest { + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static ElasticsearchContainer elasticsearch; + + static HttpHost httpHost; + + static ElasticsearchClient client; + static ElasticsearchAsyncClient asyncClient; + + @BeforeAll + static void setUp() { + elasticsearch = + new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.17.2"); + // limit memory usage + elasticsearch.withEnv("ES_JAVA_OPTS", "-Xmx256m -Xms256m"); + elasticsearch.start(); + + httpHost = HttpHost.create(elasticsearch.getHttpHostAddress()); + + RestClient restClient = + RestClient.builder(httpHost) + .setRequestConfigCallback( + builder -> + builder + .setConnectTimeout(Integer.MAX_VALUE) + .setSocketTimeout(Integer.MAX_VALUE)) + .build(); + + ElasticsearchTransport transport = + new RestClientTransport(restClient, new JacksonJsonpMapper()); + client = new ElasticsearchClient(transport); + asyncClient = new ElasticsearchAsyncClient(transport); + } + + @AfterAll + static void cleanUp() { + elasticsearch.stop(); + } + + private static String userAgent() { + return "elastic-java/" + Version.VERSION + " (Java/" + System.getProperty("java.version") + ")"; + } + + @Test + public void elasticsearchStatus() throws IOException { + InfoResponse response = client.info(); + Assertions.assertEquals(response.version().number(), "7.17.2"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("info") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.DB_OPERATION, "info"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort())), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.USER_AGENT_ORIGINAL, userAgent()), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)))); + } + + @Test + public void elasticsearchIndex() throws IOException { + client.index( + r -> + r.id("test-id") + .index("test-index") + .document(new Person("person-name")) + .timeout(t -> t.time("10s"))); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("index") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.DB_OPERATION, "index"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "PUT"), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/test-index/_doc/test-id?timeout=10s"), + equalTo( + AttributeKey.stringKey("db.elasticsearch.path_parts.index"), + "test-index"), + equalTo( + AttributeKey.stringKey("db.elasticsearch.path_parts.id"), + "test-id")), + span -> + span.hasName("PUT") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "PUT"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/test-index/_doc/test-id?timeout=10s"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 201L), + equalTo(SemanticAttributes.USER_AGENT_ORIGINAL, userAgent()), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)))); + } + + @Test + public void elasticsearchStatusAsync() throws Exception { + CountDownLatch countDownLatch = new CountDownLatch(1); + AsyncRequest request = new AsyncRequest(); + + runWithSpan( + "parent", + () -> + asyncClient + .info() + .thenAccept( + infoResponse -> + runWithSpan( + "callback", + () -> { + request.setResponse(infoResponse); + countDownLatch.countDown(); + }))); + //noinspection ResultOfMethodCallIgnored + countDownLatch.await(10, TimeUnit.SECONDS); + + Assertions.assertEquals(request.getResponse().version().number(), "7.17.2"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("info") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.DB_OPERATION, "info"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.USER_AGENT_ORIGINAL, userAgent()), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)), + span -> + span.hasName("callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + private static class AsyncRequest { + volatile InfoResponse response = null; + + public InfoResponse getResponse() { + return response; + } + + public void setResponse(InfoResponse response) { + this.response = response; + } + } + + private static class Person { + public final String name; + + Person(String name) { + this.name = name; + } + + @SuppressWarnings("unused") + public String getName() { + return name; + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java index 138ed7260b94..72d9e51368d8 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.elasticsearch.ElasticsearchContainer; -public class ElasticsearchRest5Test { +class ElasticsearchRest5Test { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -92,8 +92,11 @@ void elasticsearchStatus() throws IOException { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") @@ -170,8 +173,11 @@ public void onFailure(Exception e) { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java index 178a2351235f..e1120bedaa2c 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.elasticsearch.ElasticsearchContainer; -public class ElasticsearchRest6Test { +class ElasticsearchRest6Test { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -83,8 +83,11 @@ public void elasticsearchStatus() throws IOException { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") @@ -160,8 +163,11 @@ public void onFailure(Exception e) { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts index 6e04bbed3905..e6d7dfc9c71b 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { testImplementation("org.apache.logging.log4j:log4j-core:2.11.0") testImplementation("org.apache.logging.log4j:log4j-api:2.11.0") + testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("org.testcontainers:elasticsearch") } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java index bc51e5757636..5143c0151b8e 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java @@ -14,8 +14,10 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.util.VirtualField; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchEndpointDefinition; import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchRestRequest; import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.RestResponseListener; import net.bytebuddy.asm.Advice; @@ -59,7 +61,14 @@ public static void onEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - otelRequest = ElasticsearchRestRequest.create(request.getMethod(), request.getEndpoint()); + VirtualField virtualField = + VirtualField.find(Request.class, ElasticsearchEndpointDefinition.class); + otelRequest = + ElasticsearchRestRequest.create( + request.getMethod(), + request.getEndpoint(), + virtualField.get(request), + request.getEntity()); if (!instrumenter().shouldStart(parentContext, otelRequest)) { return; } @@ -97,7 +106,15 @@ public static void onEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - otelRequest = ElasticsearchRestRequest.create(request.getMethod(), request.getEndpoint()); + VirtualField virtualField = + VirtualField.find(Request.class, ElasticsearchEndpointDefinition.class); + + otelRequest = + ElasticsearchRestRequest.create( + request.getMethod(), + request.getEndpoint(), + virtualField.get(request), + request.getEntity()); if (!instrumenter().shouldStart(parentContext, otelRequest)) { return; } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy deleted file mode 100644 index 299795ddf1cb..000000000000 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import groovy.json.JsonSlurper -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.http.HttpHost -import org.apache.http.client.config.RequestConfig -import org.apache.http.util.EntityUtils -import org.elasticsearch.client.Request -import org.elasticsearch.client.Response -import org.elasticsearch.client.ResponseListener -import org.elasticsearch.client.RestClient -import org.elasticsearch.client.RestClientBuilder -import org.testcontainers.elasticsearch.ElasticsearchContainer -import spock.lang.Shared - -import java.util.concurrent.CountDownLatch - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class ElasticsearchRest7Test extends AgentInstrumentationSpecification { - @Shared - ElasticsearchContainer elasticsearch - - @Shared - HttpHost httpHost - - @Shared - RestClient client - - def setupSpec() { - elasticsearch = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2") - // limit memory usage - elasticsearch.withEnv("ES_JAVA_OPTS", "-Xmx256m -Xms256m") - elasticsearch.start() - - httpHost = HttpHost.create(elasticsearch.getHttpHostAddress()) - client = RestClient.builder(httpHost) - .setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { - @Override - RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder builder) { - return builder.setConnectTimeout(Integer.MAX_VALUE).setSocketTimeout(Integer.MAX_VALUE) - } - }) - .build() - } - - def cleanupSpec() { - elasticsearch.stop() - } - - def "test elasticsearch status"() { - setup: - Response response = client.performRequest(new Request("GET", "_cluster/health")) - - Map result = new JsonSlurper().parseText(EntityUtils.toString(response.entity)) - - expect: - result.status == "green" - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "GET" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.DB_SYSTEM" "elasticsearch" - "$SemanticAttributes.DB_OPERATION" "GET" - "$SemanticAttributes.DB_STATEMENT" "GET _cluster/health" - } - } - span(1) { - name "GET" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_PEER_NAME" httpHost.hostName - "$SemanticAttributes.NET_PEER_PORT" httpHost.port - "$SemanticAttributes.HTTP_METHOD" "GET" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - } - } - } - - def "test elasticsearch status async"() { - setup: - Response requestResponse = null - Exception exception = null - CountDownLatch countDownLatch = new CountDownLatch(1) - ResponseListener responseListener = new ResponseListener() { - @Override - void onSuccess(Response response) { - runWithSpan("callback") { - requestResponse = response - countDownLatch.countDown() - } - } - - @Override - void onFailure(Exception e) { - runWithSpan("callback") { - exception = e - countDownLatch.countDown() - } - } - } - runWithSpan("parent") { - client.performRequestAsync(new Request("GET", "_cluster/health"), responseListener) - } - countDownLatch.await() - - if (exception != null) { - throw exception - } - Map result = new JsonSlurper().parseText(EntityUtils.toString(requestResponse.entity)) - - expect: - result.status == "green" - - assertTraces(1) { - trace(0, 4) { - span(0) { - name "parent" - kind INTERNAL - hasNoParent() - } - span(1) { - name "GET" - kind CLIENT - childOf(span(0)) - attributes { - "$SemanticAttributes.DB_SYSTEM" "elasticsearch" - "$SemanticAttributes.DB_OPERATION" "GET" - "$SemanticAttributes.DB_STATEMENT" "GET _cluster/health" - } - } - span(2) { - name "GET" - kind CLIENT - childOf span(1) - attributes { - "$SemanticAttributes.NET_PEER_NAME" httpHost.hostName - "$SemanticAttributes.NET_PEER_PORT" httpHost.port - "$SemanticAttributes.HTTP_METHOD" "GET" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - span(3) { - name "callback" - kind INTERNAL - childOf(span(0)) - } - } - } - } -} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java new file mode 100644 index 000000000000..535f32b84022 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java @@ -0,0 +1,211 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +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.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.http.HttpHost; +import org.assertj.core.api.AbstractLongAssert; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseListener; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +class ElasticsearchRest7Test { + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static ElasticsearchContainer elasticsearch; + + static HttpHost httpHost; + + static RestClient client; + + static ObjectMapper objectMapper; + + @BeforeAll + static void setUp() { + elasticsearch = + new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2"); + // limit memory usage + elasticsearch.withEnv("ES_JAVA_OPTS", "-Xmx256m -Xms256m"); + elasticsearch.start(); + + httpHost = HttpHost.create(elasticsearch.getHttpHostAddress()); + + client = + RestClient.builder(httpHost) + .setRequestConfigCallback( + builder -> + builder + .setConnectTimeout(Integer.MAX_VALUE) + .setSocketTimeout(Integer.MAX_VALUE)) + .build(); + + objectMapper = new ObjectMapper(); + } + + @AfterAll + static void cleanUp() { + elasticsearch.stop(); + } + + @Test + public void elasticsearchStatus() throws Exception { + Response response = client.performRequest(new Request("GET", "_cluster/health")); + Map result = objectMapper.readValue(response.getEntity().getContent(), Map.class); + Assertions.assertEquals(result.get("status"), "green"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/_cluster/health")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)))); + } + + @Test + public void elasticsearchStatusAsync() throws Exception { + AsyncRequest asyncRequest = new AsyncRequest(); + CountDownLatch countDownLatch = new CountDownLatch(1); + ResponseListener responseListener = + new ResponseListener() { + @Override + public void onSuccess(Response response) { + + runWithSpan( + "callback", + () -> { + asyncRequest.setRequestResponse(response); + countDownLatch.countDown(); + }); + } + + @Override + public void onFailure(Exception e) { + runWithSpan( + "callback", + () -> { + asyncRequest.setException(e); + countDownLatch.countDown(); + }); + } + }; + + runWithSpan( + "parent", + () -> client.performRequestAsync(new Request("GET", "_cluster/health"), responseListener)); + //noinspection ResultOfMethodCallIgnored + countDownLatch.await(10, TimeUnit.SECONDS); + + if (asyncRequest.getException() != null) { + throw asyncRequest.getException(); + } + + Map result = + objectMapper.readValue( + asyncRequest.getRequestResponse().getEntity().getContent(), Map.class); + Assertions.assertEquals(result.get("status"), "green"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/_cluster/health")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)), + span -> + span.hasName("callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + private static class AsyncRequest { + volatile Response requestResponse = null; + volatile Exception exception = null; + + public Response getRequestResponse() { + return requestResponse; + } + + public void setRequestResponse(Response requestResponse) { + this.requestResponse = requestResponse; + } + + public Exception getException() { + return exception; + } + + public void setException(Exception exception) { + this.exception = exception; + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java new file mode 100644 index 000000000000..87d4724d9141 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.instrumentation.api.internal.cache.Cache; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nullable; +import org.apache.http.HttpHost; +import org.elasticsearch.client.Response; + +public class ElasticsearchClientAttributeExtractor + implements AttributesExtractor { + + private static final String PATH_PARTS_ATTRIBUTE_PREFIX = "db.elasticsearch.path_parts."; + + private static final Cache> pathPartKeysCache = Cache.bounded(64); + + private static void setServerAttributes(AttributesBuilder attributes, Response response) { + HttpHost host = response.getHost(); + if (host != null) { + if (SemconvStability.emitStableHttpSemconv()) { + internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, host.getHostName()); + internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) host.getPort()); + } + if (SemconvStability.emitOldHttpSemconv()) { + internalSet(attributes, SemanticAttributes.NET_PEER_NAME, host.getHostName()); + internalSet(attributes, SemanticAttributes.NET_PEER_PORT, (long) host.getPort()); + } + } + } + + private static void setUrlAttribute(AttributesBuilder attributes, Response response) { + String uri = response.getRequestLine().getUri(); + uri = uri.startsWith("/") ? uri : "/" + uri; + String fullUrl = response.getHost().toURI() + uri; + + if (SemconvStability.emitStableHttpSemconv()) { + internalSet(attributes, UrlAttributes.URL_FULL, fullUrl); + } + + if (SemconvStability.emitOldHttpSemconv()) { + internalSet(attributes, SemanticAttributes.HTTP_URL, fullUrl); + } + } + + private static void setPathPartsAttributes( + AttributesBuilder attributes, ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition endpointDef = request.getEndpointDefinition(); + if (endpointDef == null) { + return; + } + + endpointDef.processPathParts( + request.getEndpoint(), + (key, value) -> { + AttributeKey attributeKey = + pathPartKeysCache.computeIfAbsent( + key, k -> AttributeKey.stringKey(PATH_PARTS_ATTRIBUTE_PREFIX + k)); + internalSet(attributes, attributeKey, value); + }); + } + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, ElasticsearchRestRequest request) { + internalSet(attributes, SemanticAttributes.HTTP_METHOD, request.getMethod()); + setPathPartsAttributes(attributes, request); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + ElasticsearchRestRequest request, + @Nullable Response response, + @Nullable Throwable error) { + if (response != null) { + + setUrlAttribute(attributes, response); + setServerAttributes(attributes, response); + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java new file mode 100644 index 000000000000..39e324b82548 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static java.util.logging.Level.FINE; + +import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter; +import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.http.HttpEntity; + +final class ElasticsearchDbAttributesGetter + implements DbClientAttributesGetter { + + private static final boolean CAPTURE_SEARCH_QUERY = + InstrumentationConfig.get() + .getBoolean("otel.instrumentation.elasticsearch.capture-search-query", false); + + private static final Logger logger = + Logger.getLogger(ElasticsearchDbAttributesGetter.class.getName()); + + @Override + public String getSystem(ElasticsearchRestRequest request) { + return SemanticAttributes.DbSystemValues.ELASTICSEARCH; + } + + @Override + @Nullable + public String getUser(ElasticsearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String getName(ElasticsearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String getConnectionString(ElasticsearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String getStatement(ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition epDefinition = request.getEndpointDefinition(); + HttpEntity httpEntity = request.getHttpEntity(); + if (CAPTURE_SEARCH_QUERY + && epDefinition != null + && epDefinition.isSearchEndpoint() + && httpEntity != null + && httpEntity.isRepeatable()) { + // Retrieve HTTP body for search-type Elasticsearch requests when CAPTURE_SEARCH_QUERY is + // enabled. + try { + return new BufferedReader( + new InputStreamReader(httpEntity.getContent(), StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining()); + } catch (IOException e) { + logger.log(FINE, "Failed reading HTTP body content.", e); + } + } + return null; + } + + @Override + @Nullable + public String getOperation(ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition endpointDefinition = request.getEndpointDefinition(); + return endpointDefinition != null ? endpointDefinition.getEndpointName() : null; + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java new file mode 100644 index 000000000000..3f26d19fc065 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java @@ -0,0 +1,177 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static java.util.Collections.unmodifiableList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +public final class ElasticsearchEndpointDefinition { + + private static final String UNDERSCORE_REPLACEMENT = "0"; + + private final String endpointName; + private final List routes; + + private final boolean isSearchEndpoint; + + public ElasticsearchEndpointDefinition( + String endpointName, String[] routes, boolean isSearchEndpoint) { + this.endpointName = endpointName; + this.routes = + unmodifiableList(Arrays.stream(routes).map(Route::new).collect(Collectors.toList())); + this.isSearchEndpoint = isSearchEndpoint; + } + + @Nullable + public String getEndpointName() { + return endpointName; + } + + public boolean isSearchEndpoint() { + return isSearchEndpoint; + } + + public void processPathParts(String urlPath, BiConsumer consumer) { + for (Route route : routes) { + if (route.hasParameters()) { + Matcher matcher = route.createMatcher(urlPath); + if (matcher.find()) { + for (String key : route.getPathPartNames()) { + String value = matcher.group(key); + if (key.contains(UNDERSCORE_REPLACEMENT)) { + // replace underscore back + key = key.replace(UNDERSCORE_REPLACEMENT, "_"); + } + consumer.accept(key, value); + } + return; + } + } + } + } + + List getRoutes() { + return routes; + } + + static final class Route { + private final String name; + private final boolean hasParameters; + + private volatile EndpointPattern epPattern; + + public Route(String name) { + this.name = name; + this.hasParameters = name.contains("{") && name.contains("}"); + } + + String getName() { + return name; + } + + boolean hasParameters() { + return hasParameters; + } + + List getPathPartNames() { + return getEndpointPattern().getPathPartNames(); + } + + Matcher createMatcher(String urlPath) { + return getEndpointPattern().getPattern().matcher(urlPath); + } + + private EndpointPattern getEndpointPattern() { + // Intentionally NOT synchronizing here to avoid synchronization overhead. + // Main purpose here is to cache the pattern without the need for strict thread-safety. + if (epPattern == null) { + epPattern = new EndpointPattern(this); + } + + return epPattern; + } + } + + static final class EndpointPattern { + private static final Pattern PATH_PART_NAMES_PATTERN = Pattern.compile("\\{([^}]+)}"); + private final Pattern pattern; + private final List pathPartNames; + + /** + * Creates, compiles and caches a regular expression pattern and retrieves a set of + * pathPartNames (names of the URL path parameters) for this route. + * + *

The regex pattern is later being used to match against a URL path to retrieve the URL path + * parameters for that route pattern using named regex capture groups. + */ + private EndpointPattern(Route route) { + pattern = buildRegexPattern(route.getName()); + + if (route.hasParameters()) { + pathPartNames = new ArrayList<>(); + Matcher matcher = PATH_PART_NAMES_PATTERN.matcher(route.getName()); + while (matcher.find()) { + String groupName = matcher.group(1); + + if (groupName != null) { + groupName = groupName.replace("_", UNDERSCORE_REPLACEMENT); + pathPartNames.add(groupName); + } + } + } else { + pathPartNames = Collections.emptyList(); + } + } + + /** Builds a regex pattern from the parameterized route pattern. */ + static Pattern buildRegexPattern(String routeStr) { + StringBuilder regexStr = new StringBuilder(); + regexStr.append('^'); + int startIdx = routeStr.indexOf("{"); + while (startIdx >= 0) { + regexStr.append(routeStr.substring(0, startIdx)); + + int endIndex = routeStr.indexOf("}"); + if (endIndex <= startIdx + 1) { + break; + } + + // Append named capture group. + // If group name contains an underscore `_` it is being replaced with `0`, + // because `_` is not allowed in capture group names. + regexStr.append("(?<"); + regexStr.append( + routeStr.substring(startIdx + 1, endIndex).replace("_", UNDERSCORE_REPLACEMENT)); + regexStr.append(">[^/]+)"); + + routeStr = routeStr.substring(endIndex + 1); + startIdx = routeStr.indexOf("{"); + } + + regexStr.append(routeStr); + regexStr.append('$'); + + return Pattern.compile(regexStr.toString()); + } + + Pattern getPattern() { + return pattern; + } + + List getPathPartNames() { + return pathPartNames; + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java deleted file mode 100644 index e6ae243108c8..000000000000 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; - -import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import javax.annotation.Nullable; - -final class ElasticsearchRestAttributesGetter - implements DbClientAttributesGetter { - - @Override - public String getSystem(ElasticsearchRestRequest request) { - return SemanticAttributes.DbSystemValues.ELASTICSEARCH; - } - - @Override - @Nullable - public String getUser(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public String getName(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public String getConnectionString(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public String getStatement(ElasticsearchRestRequest request) { - return request.getMethod() + " " + request.getOperation(); - } - - @Override - @Nullable - public String getOperation(ElasticsearchRestRequest request) { - return request.getMethod(); - } -} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java index e624a3a2d388..4a06e099f07b 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java @@ -9,32 +9,25 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; -import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; import org.elasticsearch.client.Response; public final class ElasticsearchRestInstrumenterFactory { + private ElasticsearchRestInstrumenterFactory() {} + public static Instrumenter create( String instrumentationName) { - ElasticsearchRestAttributesGetter dbClientAttributesGetter = - new ElasticsearchRestAttributesGetter(); - ElasticsearchRestNetResponseAttributesGetter netAttributesGetter = - new ElasticsearchRestNetResponseAttributesGetter(); + ElasticsearchDbAttributesGetter dbClientAttributesGetter = + new ElasticsearchDbAttributesGetter(); + ElasticsearchClientAttributeExtractor esClientAtrributesExtractor = + new ElasticsearchClientAttributeExtractor(); + ElasticsearchSpanNameExtractor nameExtractor = + new ElasticsearchSpanNameExtractor(dbClientAttributesGetter); return Instrumenter.builder( - GlobalOpenTelemetry.get(), - instrumentationName, - DbClientSpanNameExtractor.create(dbClientAttributesGetter)) + GlobalOpenTelemetry.get(), instrumentationName, nameExtractor) .addAttributesExtractor(DbClientAttributesExtractor.create(dbClientAttributesGetter)) - .addAttributesExtractor(NetClientAttributesExtractor.create(netAttributesGetter)) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) + .addAttributesExtractor(esClientAtrributesExtractor) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } - - private ElasticsearchRestInstrumenterFactory() {} } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java deleted file mode 100644 index 7716a2b8562c..000000000000 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; - -import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; -import java.net.Inet6Address; -import javax.annotation.Nullable; -import org.elasticsearch.client.Response; - -final class ElasticsearchRestNetResponseAttributesGetter - implements NetClientAttributesGetter { - - @Override - @Nullable - public String getServerAddress(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public Integer getServerPort(ElasticsearchRestRequest request) { - return null; - } - - @Nullable - @Override - public String getSockFamily( - ElasticsearchRestRequest elasticsearchRestRequest, @Nullable Response response) { - if (response != null && response.getHost().getAddress() instanceof Inet6Address) { - return "inet6"; - } - return null; - } - - @Override - @Nullable - public String getServerSocketAddress( - ElasticsearchRestRequest request, @Nullable Response response) { - if (response != null && response.getHost().getAddress() != null) { - return response.getHost().getAddress().getHostAddress(); - } - return null; - } -} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java index 3298c1a40dc7..1debc037d775 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java @@ -6,15 +6,31 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; +import org.apache.http.HttpEntity; @AutoValue public abstract class ElasticsearchRestRequest { public static ElasticsearchRestRequest create(String method, String endpoint) { - return new AutoValue_ElasticsearchRestRequest(method, endpoint); + return create(method, endpoint, null, null); + } + + public static ElasticsearchRestRequest create( + String method, + String endpoint, + @Nullable ElasticsearchEndpointDefinition endpointDefinition, + @Nullable HttpEntity httpEntity) { + return new AutoValue_ElasticsearchRestRequest(method, endpoint, endpointDefinition, httpEntity); } public abstract String getMethod(); - public abstract String getOperation(); + public abstract String getEndpoint(); + + @Nullable + public abstract ElasticsearchEndpointDefinition getEndpointDefinition(); + + @Nullable + public abstract HttpEntity getHttpEntity(); } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java new file mode 100644 index 000000000000..152e3e21b428 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; + +public class ElasticsearchSpanNameExtractor implements SpanNameExtractor { + + private final ElasticsearchDbAttributesGetter dbAttributesGetter; + + public ElasticsearchSpanNameExtractor(ElasticsearchDbAttributesGetter dbAttributesGetter) { + this.dbAttributesGetter = dbAttributesGetter; + } + + @Override + public String extract(ElasticsearchRestRequest elasticsearchRestRequest) { + String name = dbAttributesGetter.getOperation(elasticsearchRestRequest); + return name != null ? name : elasticsearchRestRequest.getMethod(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index cdb637ca3426..bc467f48de81 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -223,6 +223,8 @@ hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-commo hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-5.0:javaagent") hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-5.3:javaagent") hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-6.0:javaagent") +hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-api-client-7.16:javaagent") +hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-api-client-7.16:javaagent-unit-tests") hideFromDependabot(":instrumentation:executors:bootstrap") hideFromDependabot(":instrumentation:executors:javaagent") hideFromDependabot(":instrumentation:executors:testing") From 11aba268d8a97a2f20284bee173f518f9d3e7fae Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 6 Jul 2023 13:50:35 +0300 Subject: [PATCH 59/95] Remove testcontainers version, it is set via bom (#8880) --- .../cassandra/cassandra-4-common/testing/build.gradle.kts | 2 +- instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts | 2 +- .../rocketmq-client-5.0/testing/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts b/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts index 3d12c28f94e9..8147a0167158 100644 --- a/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts +++ b/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts @@ -5,6 +5,6 @@ plugins { dependencies { api(project(":testing-common")) - implementation("org.testcontainers:testcontainers:1.17.5") + implementation("org.testcontainers:testcontainers") implementation("com.datastax.oss:java-driver-core:4.0.0") } diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts index df33c367c452..21e666b9fea5 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { library("org.apache.pulsar:pulsar-client:2.8.0") testImplementation("javax.annotation:javax.annotation-api:1.3.2") - testImplementation("org.testcontainers:pulsar:1.17.1") + testImplementation("org.testcontainers:pulsar") testImplementation("org.apache.pulsar:pulsar-client-admin:2.8.0") } diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts index 287d950d28de..0483951a9630 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts @@ -7,6 +7,6 @@ dependencies { // earlier versions have bugs that may make tests flaky. implementation("org.apache.rocketmq:rocketmq-client-java:5.0.2") - implementation("org.testcontainers:testcontainers:1.17.5") + implementation("org.testcontainers:testcontainers") implementation("io.opentelemetry:opentelemetry-api") } From 79b5140a112a4b9ed06667e6386464971a12c12f Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 6 Jul 2023 13:51:41 +0300 Subject: [PATCH 60/95] Extract common content length parsing code into method (#8884) --- .../Servlet3SnippetInjectingResponseWrapper.java | 15 ++++++--------- .../Servlet5SnippetInjectingResponseWrapper.java | 15 ++++++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java index 51432cd3d376..57dbb09401e7 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java @@ -75,19 +75,17 @@ public boolean containsHeader(String name) { @Override public void setHeader(String name, String value) { - // checking content-type is just an optimization to avoid unnecessary parsing - if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { - try { - contentLength = Long.parseLong(value); - } catch (NumberFormatException ex) { - logger.log(FINE, "NumberFormatException", ex); - } - } + handleHeader(name, value); super.setHeader(name, value); } @Override public void addHeader(String name, String value) { + handleHeader(name, value); + super.addHeader(name, value); + } + + private void handleHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { @@ -96,7 +94,6 @@ public void addHeader(String name, String value) { logger.log(FINE, "Failed to parse the Content-Length header", ex); } } - super.addHeader(name, value); } @Override diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java index d0ef8d42e375..5a9f02acc748 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java @@ -66,19 +66,17 @@ public boolean containsHeader(String name) { @Override public void setHeader(String name, String value) { - // checking content-type is just an optimization to avoid unnecessary parsing - if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { - try { - contentLength = Long.parseLong(value); - } catch (NumberFormatException ex) { - logger.log(FINE, "NumberFormatException", ex); - } - } + handleHeader(name, value); super.setHeader(name, value); } @Override public void addHeader(String name, String value) { + handleHeader(name, value); + super.addHeader(name, value); + } + + private void handleHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { @@ -87,7 +85,6 @@ public void addHeader(String name, String value) { logger.log(FINE, "Failed to parse the Content-Length header", ex); } } - super.addHeader(name, value); } @Override From c454de9ca61bd80c0ff8c48168144503ad660f84 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 6 Jul 2023 13:52:06 +0300 Subject: [PATCH 61/95] Increase cassandra connect timeout (#8879) --- .../opentelemetry/cassandra/v4/common/AbstractCassandraTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java b/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java index 9b3b06a5770f..0872af50bd25 100644 --- a/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java +++ b/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java @@ -300,6 +300,7 @@ protected CqlSession getSession(String keyspace) { DriverConfigLoader configLoader = DefaultDriverConfigLoader.builder() .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(0)) + .withDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, Duration.ofSeconds(10)) .build(); return wrap( CqlSession.builder() From d701967f2d7d3697cc95ca73e2dd34819899f372 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 6 Jul 2023 14:14:23 +0300 Subject: [PATCH 62/95] Free more disk space for running tests (#8882) --- .github/workflows/build-common.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 43c86fb52f3d..721a7368fb8c 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -162,6 +162,14 @@ jobs: vm: openj9 fail-fast: false steps: + # tests may fail without freeing up more disk space on the runner + - name: Free disk space + run: | + df -h + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + df -h + - uses: actions/checkout@v3 - id: setup-test-java @@ -285,7 +293,7 @@ jobs: run: git config --system core.longpaths true if: matrix.os == 'windows-latest' - # downloading the liberty image (a bit over 10gb) fails without freeing up more disk space on the runner + # downloading the liberty image (a bit over 10gb) fails without freeing up more disk space on the runner - name: Free disk space run: | df -h From 40938cf9e22834f9e6a48f47ea30e72d2ec1db0d Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 6 Jul 2023 14:17:04 +0300 Subject: [PATCH 63/95] Fix flaky ratpack http client test (#8881) --- .../client/InstrumentedHttpClientTest.groovy | 164 +++++++++--------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy index dbf11883d7ed..408611002d18 100644 --- a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy @@ -86,35 +86,35 @@ class InstrumentedHttpClientTest extends Specification { } app.test { httpClient -> - "bar" == httpClient.get("foo").body.text - } - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" && it.kind == CLIENT } - def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "GET /bar" && it.kind == SERVER } - - spanData.traceId == spanClientData.traceId - spanData.traceId == spanDataApi.traceId - - spanData.kind == SERVER - spanClientData.kind == CLIENT - def atts = spanClientData.attributes.asMap() - atts[HTTP_ROUTE] == "/bar" - atts[HTTP_METHOD] == "GET" - atts[HTTP_STATUS_CODE] == 200L - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/foo" - attributes[SemanticAttributes.HTTP_TARGET] == "/foo" - attributes[HTTP_METHOD] == "GET" - attributes[HTTP_STATUS_CODE] == 200L - - def attsApi = spanDataApi.attributes.asMap() - attsApi[HTTP_ROUTE] == "/bar" - attsApi[SemanticAttributes.HTTP_TARGET] == "/bar" - attsApi[HTTP_METHOD] == "GET" - attsApi[HTTP_STATUS_CODE] == 200L + assert "bar" == httpClient.get("foo").body.text + + new PollingConditions().eventually { + def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } + def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" && it.kind == CLIENT } + def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "GET /bar" && it.kind == SERVER } + + spanData.traceId == spanClientData.traceId + spanData.traceId == spanDataApi.traceId + + spanData.kind == SERVER + spanClientData.kind == CLIENT + def atts = spanClientData.attributes.asMap() + atts[HTTP_ROUTE] == "/bar" + atts[HTTP_METHOD] == "GET" + atts[HTTP_STATUS_CODE] == 200L + + def attributes = spanData.attributes.asMap() + attributes[HTTP_ROUTE] == "/foo" + attributes[SemanticAttributes.HTTP_TARGET] == "/foo" + attributes[HTTP_METHOD] == "GET" + attributes[HTTP_STATUS_CODE] == 200L + + def attsApi = spanDataApi.attributes.asMap() + attsApi[HTTP_ROUTE] == "/bar" + attsApi[SemanticAttributes.HTTP_TARGET] == "/bar" + attsApi[HTTP_METHOD] == "GET" + attsApi[HTTP_STATUS_CODE] == 200L + } } } @@ -148,38 +148,38 @@ class InstrumentedHttpClientTest extends Specification { } app.test { httpClient -> - "hello" == httpClient.get("path-name").body.text + assert "hello" == httpClient.get("path-name").body.text latch.await(1, TimeUnit.SECONDS) - } - new PollingConditions().eventually { - spanExporter.finishedSpanItems.size() == 3 - def spanData = spanExporter.finishedSpanItems.find { spanData -> spanData.name == "GET /path-name" } - def spanClientData1 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/foo" } - def spanClientData2 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/bar" } - - spanData.traceId == spanClientData1.traceId - spanData.traceId == spanClientData2.traceId - - spanData.kind == SERVER - - spanClientData1.kind == CLIENT - def atts = spanClientData1.attributes.asMap() - atts[HTTP_ROUTE] == "/foo" - atts[HTTP_METHOD] == "GET" - atts[HTTP_STATUS_CODE] == 200L - - spanClientData2.kind == CLIENT - def atts2 = spanClientData2.attributes.asMap() - atts2[HTTP_ROUTE] == "/bar" - atts2[HTTP_METHOD] == "GET" - atts2[HTTP_STATUS_CODE] == 200L - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/path-name" - attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" - attributes[HTTP_METHOD] == "GET" - attributes[HTTP_STATUS_CODE] == 200L + new PollingConditions().eventually { + spanExporter.finishedSpanItems.size() == 3 + def spanData = spanExporter.finishedSpanItems.find { spanData -> spanData.name == "GET /path-name" } + def spanClientData1 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/foo" } + def spanClientData2 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/bar" } + + spanData.traceId == spanClientData1.traceId + spanData.traceId == spanClientData2.traceId + + spanData.kind == SERVER + + spanClientData1.kind == CLIENT + def atts = spanClientData1.attributes.asMap() + atts[HTTP_ROUTE] == "/foo" + atts[HTTP_METHOD] == "GET" + atts[HTTP_STATUS_CODE] == 200L + + spanClientData2.kind == CLIENT + def atts2 = spanClientData2.attributes.asMap() + atts2[HTTP_ROUTE] == "/bar" + atts2[HTTP_METHOD] == "GET" + atts2[HTTP_STATUS_CODE] == 200L + + def attributes = spanData.attributes.asMap() + attributes[HTTP_ROUTE] == "/path-name" + attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" + attributes[HTTP_METHOD] == "GET" + attributes[HTTP_STATUS_CODE] == 200L + } } } @@ -214,28 +214,30 @@ class InstrumentedHttpClientTest extends Specification { } } - app.test { httpClient -> "error" == httpClient.get("path-name").body.text } - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /path-name" } - def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" } - - spanData.traceId == spanClientData.traceId - - spanData.kind == SERVER - spanClientData.kind == CLIENT - def atts = spanClientData.attributes.asMap() - atts[HTTP_ROUTE] == "/foo" - atts[HTTP_METHOD] == "GET" - atts[HTTP_STATUS_CODE] == null - spanClientData.status.statusCode == StatusCode.ERROR - spanClientData.events.first().name == "exception" - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/path-name" - attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" - attributes[HTTP_METHOD] == "GET" - attributes[HTTP_STATUS_CODE] == 200L + app.test { httpClient -> + assert "error" == httpClient.get("path-name").body.text + + new PollingConditions().eventually { + def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /path-name" } + def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" } + + spanData.traceId == spanClientData.traceId + + spanData.kind == SERVER + spanClientData.kind == CLIENT + def atts = spanClientData.attributes.asMap() + atts[HTTP_ROUTE] == "/foo" + atts[HTTP_METHOD] == "GET" + atts[HTTP_STATUS_CODE] == null + spanClientData.status.statusCode == StatusCode.ERROR + spanClientData.events.first().name == "exception" + + def attributes = spanData.attributes.asMap() + attributes[HTTP_ROUTE] == "/path-name" + attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" + attributes[HTTP_METHOD] == "GET" + attributes[HTTP_STATUS_CODE] == 200L + } } } From ba2f8d209f96706a122b4a22aa5f90b3814f257a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Thu, 6 Jul 2023 13:17:13 +0200 Subject: [PATCH 64/95] aws-sdk-1.1: Copy SQS plugin/NoMuzzle approach from 2.2 (#8866) Co-authored-by: Mateusz Rzeszutek --- .../aws-sdk-1.11/javaagent/build.gradle.kts | 16 +++ .../awssdk/v1_11/SqsAdviceBridge.java | 15 +++ .../AbstractAwsSdkInstrumentationModule.java | 60 +++++++++++ .../v1_11/AwsSdkInstrumentationModule.java | 2 +- .../v1_11/SqsInstrumentationModule.java | 38 +++++++ .../aws-sdk-1.11/library/build.gradle.kts | 3 +- .../awssdk/v1_11/PluginImplUtil.java | 56 +++++++++++ .../awssdk/v1_11/SqsAccess.java | 31 ++++++ .../instrumentation/awssdk/v1_11/SqsImpl.java | 70 +++++++++++++ .../awssdk/v1_11/SqsMessageAccess.java | 63 ------------ .../v1_11/SqsReceiveMessageRequestAccess.java | 99 ------------------- .../v1_11/SqsReceiveMessageResultAccess.java | 64 ------------ .../awssdk/v1_11/TracingRequestHandler.java | 31 +----- .../awssdk/v2_2/PluginImplUtil.java | 29 +++--- 14 files changed, 311 insertions(+), 266 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java delete mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java delete mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java delete mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts index b654433cefe6..e04a0ce1b5a0 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts @@ -15,6 +15,22 @@ muzzle { module.set("aws-java-sdk-core") versions.set("[1.10.33,)") assertInverse.set(true) + + excludeInstrumentationName("aws-sdk-1.11-sqs") + } + + fail { + group.set("com.amazonaws") + module.set("aws-java-sdk-core") + versions.set("[1.10.33,)") + + excludeInstrumentationName("aws-sdk-1.11-core") + } + + pass { + group.set("com.amazonaws") + module.set("aws-java-sdk-sqs") + versions.set("[1.10.33,)") } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java new file mode 100644 index 000000000000..efa6fe9a3428 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java @@ -0,0 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +public final class SqsAdviceBridge { + private SqsAdviceBridge() {} + + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SqsImpl.class.getName() + " referencing for muzzle, should never be actually called"); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java new file mode 100644 index 000000000000..0e515eb28abf --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +// TODO: Copy & paste with only trivial adaptions from v2 +abstract class AbstractAwsSdkInstrumentationModule extends InstrumentationModule { + + protected AbstractAwsSdkInstrumentationModule(String additionalInstrumentationName) { + super("aws-sdk", "aws-sdk-1.11", additionalInstrumentationName); + } + + @Override + public boolean isHelperClass(String className) { + return className.startsWith("io.opentelemetry.contrib.awsxray."); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // We don't actually transform it but want to make sure we only apply the instrumentation when + // our key dependency is present. + return hasClassesNamed("com.amazonaws.AmazonWebServiceClient"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ResourceInjectingTypeInstrumentation()); + } + + abstract void doTransform(TypeTransformer transformer); + + // A type instrumentation is needed to trigger resource injection. + public class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure + // our interceptor service definition is injected as early as possible if we typematch against + // it. + return named("com.amazonaws.AmazonWebServiceClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + doTransform(transformer); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java index 99d72487993e..048bbdfc41a6 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java @@ -15,7 +15,7 @@ @AutoService(InstrumentationModule.class) public class AwsSdkInstrumentationModule extends InstrumentationModule { public AwsSdkInstrumentationModule() { - super("aws-sdk", "aws-sdk-1.11"); + super("aws-sdk", "aws-sdk-1.11", "aws-sdk-1.11-core"); } @Override diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java new file mode 100644 index 000000000000..cc61cf6d9cc0 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; + +import static net.bytebuddy.matcher.ElementMatchers.none; + +import com.google.auto.service.AutoService; +import io.opentelemetry.instrumentation.awssdk.v1_11.SqsAdviceBridge; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumentationModule.class) +public class SqsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { + + public SqsInstrumentationModule() { + super("aws-sdk-1.11-sqs"); + } + + @Override + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); + } + + @SuppressWarnings("unused") + public static class RegisterAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit() { + // (indirectly) using SqsImpl class here to make sure it is available from SqsAccess + // (injected into app classloader) and checked by Muzzle + SqsAdviceBridge.referenceForMuzzleOnly(); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts index 99277d1ea885..aa9e0634a867 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts @@ -6,6 +6,8 @@ dependencies { implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") library("com.amazonaws:aws-java-sdk-core:1.11.0") + library("com.amazonaws:aws-java-sdk-sqs:1.11.106") + compileOnly(project(":muzzle")) testImplementation(project(":instrumentation:aws-sdk:aws-sdk-1.11:testing")) @@ -15,5 +17,4 @@ dependencies { testLibrary("com.amazonaws:aws-java-sdk-kinesis:1.11.106") testLibrary("com.amazonaws:aws-java-sdk-dynamodb:1.11.106") testLibrary("com.amazonaws:aws-java-sdk-sns:1.11.106") - testLibrary("com.amazonaws:aws-java-sdk-sqs:1.11.106") } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java new file mode 100644 index 000000000000..364bfffe5318 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import java.util.logging.Level; +import java.util.logging.Logger; + +final class PluginImplUtil { // TODO: Copy & paste from v2 + private PluginImplUtil() {} + + private static final Logger logger = Logger.getLogger(PluginImplUtil.class.getName()); + + /** + * Check if the given {@code moduleNameImpl} is present. + * + *

For library instrumentations, the Impls will always be available but might fail to + * load/link/initialize if the corresponding SDK classes are not on the class path. For javaagent, + * the Impl is available only when the corresponding InstrumentationModule was successfully + * applied (muzzle passed). + * + *

Note that an present-but-incompatible library can only be reliably detected by Muzzle. In + * library-mode, users need to ensure they are using a compatible SDK (component) versions + * themselves. + * + * @param implSimpleClassName The simple name of the impl class, e.g. {@code "SqsImpl"}. * + */ + static boolean isImplPresent(String implSimpleClassName) { + // Computing the full name dynamically name here because library instrumentation classes are + // relocated when embedded in the agent. + // We use getName().replace() instead of getPackage() because the latter is not guaranteed to + // work in all cases (e.g., we might be loaded into a custom classloader that doesn't handle it) + String implFullClassName = + PluginImplUtil.class.getName().replace(".PluginImplUtil", "." + implSimpleClassName); + try { + Class.forName(implFullClassName); + return true; + } catch (ClassNotFoundException | LinkageError e) { + // ClassNotFoundException will happen when muzzle disabled us in javaagent mode; LinkageError + // (most likely a NoClassDefFoundError, potentially wrapped in an ExceptionInInitializerError) + // should be thrown when the class is loaded in library mode (where the Impl class itself can + // always be found) but a dependency failed to load (most likely because the corresponding SDK + // dependency is not on the class path). + logger.log( + Level.FINE, + e, + () -> + implFullClassName + + " not present. " + + "Most likely, corresponding SDK component is either not on classpath or incompatible."); + return false; + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java new file mode 100644 index 000000000000..12fe4bae8d67 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; + +final class SqsAccess { + private SqsAccess() {} + + private static final boolean enabled = PluginImplUtil.isImplPresent("SqsImpl"); + + @NoMuzzle + static boolean afterResponse( + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + return enabled && SqsImpl.afterResponse(request, response, consumerInstrumenter); + } + + @NoMuzzle + static boolean beforeMarshalling(AmazonWebServiceRequest request) { + return enabled && SqsImpl.beforeMarshalling(request); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java new file mode 100644 index 000000000000..f4cd92757999 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.Message; +import com.amazonaws.services.sqs.model.ReceiveMessageRequest; +import com.amazonaws.services.sqs.model.ReceiveMessageResult; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +final class SqsImpl { + static { + // Force loading of SQS class; this ensures that an exception is thrown at this point when the + // SQS library is not present, which will cause SqsAccess to have enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = AmazonSQS.class.getName(); + } + + private SqsImpl() {} + + static boolean afterResponse( + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + if (response.getAwsResponse() instanceof ReceiveMessageResult) { + afterConsumerResponse(request, response, consumerInstrumenter); + return true; + } + return false; + } + + /** Create and close CONSUMER span for each message consumed. */ + private static void afterConsumerResponse( + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + ReceiveMessageResult receiveMessageResult = (ReceiveMessageResult) response.getAwsResponse(); + for (Message message : receiveMessageResult.getMessages()) { + createConsumerSpan(message, request, response, consumerInstrumenter); + } + } + + private static void createConsumerSpan( + Message message, + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + Context parentContext = SqsParentContext.ofSystemAttributes(message.getAttributes()); + Context context = consumerInstrumenter.start(parentContext, request); + consumerInstrumenter.end(context, request, response, null); + } + + static boolean beforeMarshalling(AmazonWebServiceRequest rawRequest) { + if (rawRequest instanceof ReceiveMessageRequest) { + ReceiveMessageRequest request = (ReceiveMessageRequest) rawRequest; + if (!request.getAttributeNames().contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE)) { + request.withAttributeNames(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + return true; + } + return false; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java deleted file mode 100644 index 84726bab934f..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v1_11; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collections; -import java.util.Map; -import javax.annotation.Nullable; - -/** - * Reflective access to aws-sdk-java-sqs class Message. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - */ -final class SqsMessageAccess { - - @Nullable private static final MethodHandle GET_ATTRIBUTES; - - static { - Class messageClass = null; - try { - messageClass = Class.forName("com.amazonaws.services.sqs.model.Message"); - } catch (Throwable t) { - // Ignore. - } - if (messageClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle getAttributes = null; - try { - getAttributes = lookup.findVirtual(messageClass, "getAttributes", methodType(Map.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - GET_ATTRIBUTES = getAttributes; - } else { - GET_ATTRIBUTES = null; - } - } - - @SuppressWarnings("unchecked") - static Map getAttributes(Object message) { - if (GET_ATTRIBUTES == null) { - return Collections.emptyMap(); - } - try { - return (Map) GET_ATTRIBUTES.invoke(message); - } catch (Throwable t) { - return Collections.emptyMap(); - } - } - - private SqsMessageAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java deleted file mode 100644 index 122e66f8c0d3..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v1_11; - -import static java.lang.invoke.MethodType.methodType; - -import com.amazonaws.AmazonWebServiceRequest; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - */ -final class SqsReceiveMessageRequestAccess { - - @Nullable private static final MethodHandle WITH_ATTRIBUTE_NAMES; - @Nullable private static final MethodHandle GET_ATTRIBUTE_NAMES; - - static { - Class receiveMessageRequestClass = null; - try { - receiveMessageRequestClass = - Class.forName("com.amazonaws.services.sqs.model.ReceiveMessageRequest"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageRequestClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle withAttributeNames = null; - try { - withAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "withAttributeNames", - methodType(receiveMessageRequestClass, String[].class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - WITH_ATTRIBUTE_NAMES = withAttributeNames; - - MethodHandle getAttributeNames = null; - try { - getAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, "getAttributeNames", methodType(List.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - GET_ATTRIBUTE_NAMES = getAttributeNames; - } else { - WITH_ATTRIBUTE_NAMES = null; - GET_ATTRIBUTE_NAMES = null; - } - } - - static boolean isInstance(AmazonWebServiceRequest request) { - return request - .getClass() - .getName() - .equals("com.amazonaws.services.sqs.model.ReceiveMessageRequest"); - } - - static void withAttributeNames(AmazonWebServiceRequest request, String name) { - if (WITH_ATTRIBUTE_NAMES == null) { - return; - } - try { - WITH_ATTRIBUTE_NAMES.invoke(request, name); - } catch (Throwable throwable) { - // Ignore - } - } - - @SuppressWarnings("unchecked") - static List getAttributeNames(AmazonWebServiceRequest request) { - if (GET_ATTRIBUTE_NAMES == null) { - return Collections.emptyList(); - } - try { - return (List) GET_ATTRIBUTE_NAMES.invoke(request); - } catch (Throwable t) { - return Collections.emptyList(); - } - } - - private SqsReceiveMessageRequestAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java deleted file mode 100644 index 27fb6e255f92..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v1_11; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageResult. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - */ -final class SqsReceiveMessageResultAccess { - - @Nullable private static final MethodHandle GET_MESSAGES; - - static { - Class receiveMessageResultClass = null; - try { - receiveMessageResultClass = - Class.forName("com.amazonaws.services.sqs.model.ReceiveMessageResult"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageResultClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle getMessages = null; - try { - getMessages = - lookup.findVirtual(receiveMessageResultClass, "getMessages", methodType(List.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - GET_MESSAGES = getMessages; - } else { - GET_MESSAGES = null; - } - } - - static List getMessages(Object result) { - if (GET_MESSAGES == null) { - return Collections.emptyList(); - } - try { - return (List) GET_MESSAGES.invoke(result); - } catch (Throwable t) { - return Collections.emptyList(); - } - } - - private SqsReceiveMessageResultAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java index a6755552197c..11d2f3a6fed3 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java @@ -14,7 +14,6 @@ import io.opentelemetry.context.Context; import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import java.util.List; import javax.annotation.Nullable; /** Tracing Request Handler. */ @@ -58,40 +57,18 @@ public void beforeRequest(Request request) { @Override @CanIgnoreReturnValue public AmazonWebServiceRequest beforeMarshalling(AmazonWebServiceRequest request) { - if (SqsReceiveMessageRequestAccess.isInstance(request)) { - if (!SqsReceiveMessageRequestAccess.getAttributeNames(request) - .contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE)) { - SqsReceiveMessageRequestAccess.withAttributeNames( - request, SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - } - } + // TODO: We are modifying the request in-place instead of using clone() as recommended + // by the Javadoc in the interface. + SqsAccess.beforeMarshalling(request); return request; } @Override public void afterResponse(Request request, Response response) { - if (SqsReceiveMessageRequestAccess.isInstance(request.getOriginalRequest())) { - afterConsumerResponse(request, response); - } + SqsAccess.afterResponse(request, response, consumerInstrumenter); finish(request, response, null); } - /** Create and close CONSUMER span for each message consumed. */ - private void afterConsumerResponse(Request request, Response response) { - Object receiveMessageResult = response.getAwsResponse(); - List messages = SqsReceiveMessageResultAccess.getMessages(receiveMessageResult); - for (Object message : messages) { - createConsumerSpan(message, request, response); - } - } - - private void createConsumerSpan(Object message, Request request, Response response) { - Context parentContext = - SqsParentContext.ofSystemAttributes(SqsMessageAccess.getAttributes(message)); - Context context = consumerInstrumenter.start(parentContext, request); - consumerInstrumenter.end(context, request, response, null); - } - @Override public void afterError(Request request, Response response, Exception e) { finish(request, response, e); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java index f5043ae13257..3ba1611a541e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java @@ -8,7 +8,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -final class PluginImplUtil { +final class PluginImplUtil { // TODO: Copy & pasted to v1 private PluginImplUtil() {} private static final Logger logger = Logger.getLogger(PluginImplUtil.class.getName()); @@ -16,33 +16,40 @@ private PluginImplUtil() {} /** * Check if the given {@code moduleNameImpl} is present. * - *

For library instrumentations, the Impls will always be available but might fail to load if - * the corresponding SDK classes are not on the class path. For javaagent, the Impl is available - * only when the corresponding InstrumentationModule was successfully applied (muzzle passed). + *

For library instrumentations, the Impls will always be available but might fail to + * load/link/initialize if the corresponding SDK classes are not on the class path. For javaagent, + * the Impl is available only when the corresponding InstrumentationModule was successfully + * applied (muzzle passed). * - *

Note that an present-but-incompatible library can only be detected by Muzzle. In + *

Note that an present-but-incompatible library can only be reliably detected by Muzzle. In * library-mode, users need to ensure they are using a compatible SDK (component) versions * themselves. * * @param implSimpleClassName The simple name of the impl class, e.g. {@code "SqsImpl"}. * */ static boolean isImplPresent(String implSimpleClassName) { + // Computing the full name dynamically name here because library instrumentation classes are + // relocated when embedded in the agent. // We use getName().replace() instead of getPackage() because the latter is not guaranteed to // work in all cases (e.g., we might be loaded into a custom classloader that doesn't handle it) String implFullClassName = PluginImplUtil.class.getName().replace(".PluginImplUtil", "." + implSimpleClassName); try { - // using package name here because library instrumentation classes are relocated when embedded - // in the agent Class.forName(implFullClassName); return true; } catch (ClassNotFoundException | LinkageError e) { // ClassNotFoundException will happen when muzzle disabled us in javaagent mode; LinkageError - // (most likely NoClassDefFoundError) should happen when the class is loaded in library mode - // (where it is always available) but a dependency failed to load (most likely because the - // corresponding SDK dependency is not on the class path). + // (most likely a NoClassDefFoundError, potentially wrapped in an ExceptionInInitializerError) + // should be thrown when the class is loaded in library mode (where the Impl class itself can + // always be found) but a dependency failed to load (most likely because the corresponding SDK + // dependency is not on the class path). logger.log( - Level.FINE, e, () -> implFullClassName + " not present, probably incompatible version"); + Level.FINE, + e, + () -> + implFullClassName + + " not present. " + + "Most likely, corresponding SDK component is either not on classpath or incompatible."); return false; } } From 1947c0fe4239bccebfffca7580382101525a9b63 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Thu, 6 Jul 2023 14:40:52 +0200 Subject: [PATCH 65/95] Add support for schemaUrls auto-computed from `AttributesExtrator`s (#8864) --- .../api/instrumenter/InstrumenterBuilder.java | 37 +++++++++ .../api/internal/SchemaUrlProvider.java | 22 ++++++ .../api/instrumenter/InstrumenterTest.java | 76 ++++++++++++++++++- 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java index 25dbd6ebeb2a..ff1f5d08539a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.instrumenter; import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.WARNING; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; @@ -22,11 +23,13 @@ import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; import io.opentelemetry.instrumentation.api.internal.InstrumenterBuilderAccess; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -39,6 +42,8 @@ */ public final class InstrumenterBuilder { + private static final Logger logger = Logger.getLogger(InstrumenterBuilder.class.getName()); + private static final SpanSuppressionStrategy spanSuppressionStrategy = SpanSuppressionStrategy.fromConfig( ConfigPropertiesUtil.getString( @@ -285,6 +290,7 @@ Tracer buildTracer() { if (instrumentationVersion != null) { tracerBuilder.setInstrumentationVersion(instrumentationVersion); } + String schemaUrl = getSchemaUrl(); if (schemaUrl != null) { tracerBuilder.setSchemaUrl(schemaUrl); } @@ -305,6 +311,7 @@ List buildOperationListeners() { if (instrumentationVersion != null) { meterBuilder.setInstrumentationVersion(instrumentationVersion); } + String schemaUrl = getSchemaUrl(); if (schemaUrl != null) { meterBuilder.setSchemaUrl(schemaUrl); } @@ -316,6 +323,36 @@ List buildOperationListeners() { return listeners; } + @Nullable + private String getSchemaUrl() { + // url set explicitly overrides url computed using attributes extractors + if (schemaUrl != null) { + return schemaUrl; + } + Set computedSchemaUrls = + attributesExtractors.stream() + .filter(SchemaUrlProvider.class::isInstance) + .map(SchemaUrlProvider.class::cast) + .flatMap( + provider -> { + String url = provider.internalGetSchemaUrl(); + return url == null ? Stream.of() : Stream.of(url); + }) + .collect(Collectors.toSet()); + switch (computedSchemaUrls.size()) { + case 0: + return null; + case 1: + return computedSchemaUrls.iterator().next(); + default: + logger.log( + WARNING, + "Multiple schemaUrls were detected: {0}. The built Instrumenter will have no schemaUrl assigned.", + computedSchemaUrls); + return null; + } + } + SpanSuppressor buildSpanSuppressor() { return spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors()); } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java new file mode 100644 index 000000000000..d60878294abb --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; + +/** + * Returns the OpenTelemetry schema URL associated with the {@link AttributesExtractor} that + * implements this interface. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface SchemaUrlProvider { + + @Nullable + String internalGetSchemaUrl(); +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index 3774c19a9fd2..fb9ae8622d8d 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -25,6 +25,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -113,6 +114,30 @@ public void onEnd( } } + static class AttributesExtractorWithSchemaUrl + implements AttributesExtractor, Map>, SchemaUrlProvider { + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, Map request) { + attributes.put("key", "value"); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + Map request, + @Nullable Map response, + @Nullable Throwable error) {} + + @Nullable + @Override + public String internalGetSchemaUrl() { + return "schemaUrl from extractor"; + } + } + static class LinksExtractor implements SpanLinksExtractor> { @Override @@ -583,11 +608,60 @@ void instrumentationVersion_custom() { } @Test - void schemaUrl() { + void schemaUrl_setExplicitly() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .buildInstrumenter(); + + Context context = instrumenter.start(Context.root(), emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, emptyMap(), emptyMap(), null); + + InstrumentationScopeInfo expectedLibraryInfo = + InstrumentationScopeInfo.builder("test") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .build(); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo))); + } + + @Test + void schemaUrl_computedFromExtractors() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .addAttributesExtractor(new AttributesExtractorWithSchemaUrl()) + .buildInstrumenter(); + + Context context = instrumenter.start(Context.root(), emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, emptyMap(), emptyMap(), null); + + InstrumentationScopeInfo expectedLibraryInfo = + InstrumentationScopeInfo.builder("test").setSchemaUrl("schemaUrl from extractor").build(); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo))); + } + + @Test + void schemaUrl_schemaSetExplicitlyOverridesSchemaComputedFromExtractors() { Instrumenter, Map> instrumenter = Instrumenter., Map>builder( otelTesting.getOpenTelemetry(), "test", name -> "span") .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .addAttributesExtractor(new AttributesExtractorWithSchemaUrl()) .buildInstrumenter(); Context context = instrumenter.start(Context.root(), emptyMap()); From 6f8046aedf3bd953db5165459d5fcca36eea8c70 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 6 Jul 2023 14:50:49 -0700 Subject: [PATCH 66/95] Add zeitlinger as triager (#8890) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1cf77667875f..b655415bc6bf 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,10 @@ Debug logging negatively impacts the performance of your application. See [CONTRIBUTING.md](CONTRIBUTING.md). +Triagers ([@open-telemetry/java-instrumentation-triagers](https://github.com/orgs/open-telemetry/teams/java-instrumentation-triagers)): + +- [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana Labs + Approvers ([@open-telemetry/java-instrumentation-approvers](https://github.com/orgs/open-telemetry/teams/java-instrumentation-approvers)): - [Jack Berg](https://github.com/jack-berg), New Relic From a1f623f6925a70bc2e70a93180a3da1c5f2d3257 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 7 Jul 2023 09:29:55 +0300 Subject: [PATCH 67/95] Update semconv links (#8894) --- docs/supported-libraries.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index f5b06e5ecdcc..a0f9f2a9fdab 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -142,22 +142,22 @@ These are the supported libraries and frameworks: **[3]** Controller Spans are `INTERNAL` spans capturing the controller and/or view execution. See [Suppressing controller and/or view spans](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/#suppressing-controller-andor-view-spans). -[Elasticsearch Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/specification/database/elasticsearch.md -[HTTP Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions -[HTTP Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client -[HTTP Server Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server -[HTTP Client Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-client -[RPC Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md#server-attributes -[RPC Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md#common-attributes -[RPC Server Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc-metrics.md#rpc-server -[RPC Client Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc-metrics.md#rpc-client -[Messaging Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/messaging.md -[Database Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md -[Database Pool Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/database-metrics.md -[JVM Runtime Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/runtime-environment-metrics.md#jvm-metrics -[System Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/system-metrics.md -[GraphQL Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/graphql.md -[FaaS Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/faas.md +[Elasticsearch Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/elasticsearch.md +[HTTP Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server +[HTTP Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client +[HTTP Server Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md#http-server +[HTTP Client Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md#http-client +[RPC Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md#server-attributes +[RPC Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md#client-attributes +[RPC Server Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#rpc-server +[RPC Client Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#rpc-client +[Messaging Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/messaging/messaging-spans.md +[Database Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md +[Database Pool Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-metrics.md +[JVM Runtime Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/runtime-environment-metrics.md#jvm-metrics +[System Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md +[GraphQL Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/graphql.md +[FaaS Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/faas-spans.md ## Application Servers From 84f17bd5fdbc5bcbe2db851a3e32bc621134851b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:41:47 +0200 Subject: [PATCH 68/95] Bump org.jetbrains.kotlin.jvm from 1.8.22 to 1.9.0 (#8892) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index bc467f48de81..4e3f6023c200 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,7 +6,7 @@ pluginManagement { id("com.google.cloud.tools.jib") version "3.3.2" id("com.gradle.plugin-publish") version "1.2.0" id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - id("org.jetbrains.kotlin.jvm") version "1.8.22" + id("org.jetbrains.kotlin.jvm") version "1.9.0" id("org.xbib.gradle.plugin.jflex") version "3.0.0" id("org.unbroken-dome.xjc") version "2.0.0" id("org.graalvm.buildtools.native") version "0.9.23" From e8da33c9ed3531e4f2a4510494cbf8f1975ff026 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 7 Jul 2023 10:27:15 -0400 Subject: [PATCH 69/95] Convert camel-2.20 Groovy tests to Java (#8813) --- .../camel-2.20/javaagent/build.gradle.kts | 5 + .../apachecamel/DirectCamelTest.groovy | 65 ------- .../apachecamel/DirectConfig.groovy | 45 ----- .../apachecamel/MulticastConfig.groovy | 62 ------ .../MulticastDirectCamelTest.groovy | 76 -------- .../apachecamel/RestCamelTest.groovy | 126 ------------ .../apachecamel/RestConfig.groovy | 47 ----- .../apachecamel/SingleServiceCamelTest.groovy | 74 ------- .../apachecamel/SingleServiceConfig.groovy | 35 ---- .../apachecamel/TwoServicesConfig.groovy | 54 ------ ...woServicesWithDirectClientCamelTest.groovy | 153 --------------- .../decorators/CassandraTest.groovy | 114 ----------- .../apachecamel/DirectCamelTest.java | 82 ++++++++ .../apachecamel/DirectConfig.java | 45 +++++ .../apachecamel/MulticastConfig.java | 62 ++++++ .../apachecamel/MulticastDirectCamelTest.java | 88 +++++++++ .../apachecamel/RestCamelTest.java | 136 +++++++++++++ .../apachecamel/RestConfig.java | 44 +++++ .../apachecamel/SingleServiceCamelTest.java | 77 ++++++++ .../apachecamel/SingleServiceConfig.java | 35 ++++ .../apachecamel/TwoServicesConfig.java | 54 ++++++ .../TwoServicesWithDirectClientCamelTest.java | 180 ++++++++++++++++++ .../decorators/CassandraConfig.java} | 20 +- .../apachecamel/decorators/CassandraTest.java | 125 ++++++++++++ 24 files changed, 943 insertions(+), 861 deletions(-) delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy delete mode 100644 instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java rename instrumentation/camel-2.20/javaagent/src/test/{groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy => java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java} (51%) create mode 100644 instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java diff --git a/instrumentation/camel-2.20/javaagent/build.gradle.kts b/instrumentation/camel-2.20/javaagent/build.gradle.kts index 36a816c7bab2..3a39c6d04b1c 100644 --- a/instrumentation/camel-2.20/javaagent/build.gradle.kts +++ b/instrumentation/camel-2.20/javaagent/build.gradle.kts @@ -48,6 +48,11 @@ dependencies { testImplementation("org.testcontainers:localstack") testImplementation("org.testcontainers:cassandra") + testImplementation("org.testcontainers:testcontainers") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("com.datastax.oss:java-driver-core:4.16.0") { + exclude(group = "io.dropwizard.metrics", module = "metrics-core") + } latestDepTestLibrary("org.apache.camel:camel-core:2.+") // documented limitation latestDepTestLibrary("org.apache.camel:camel-spring-boot-starter:2.+") // documented limitation diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy deleted file mode 100644 index 06079281939a..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class DirectCamelTest extends AgentInstrumentationSpecification { - - @Shared - ConfigurableApplicationContext server - - def setupSpec() { - def app = new SpringApplication(DirectConfig) - server = app.run() - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "simple direct to a single services"() { - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - template.sendBody("direct:input", "Example request") - - then: - assertTraces(1) { - trace(0, 2) { - def parent = it - it.span(0) { - name "input" - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://input" - } - } - it.span(1) { - name "receiver" - kind INTERNAL - parentSpanId parent.span(0).spanId - attributes { - "camel.uri" "direct://receiver" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy deleted file mode 100644 index 9ec2ee387d64..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class DirectConfig { - - @Bean - RouteBuilder receiverRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:receiver") - .log(LoggingLevel.INFO, "test", "RECEIVER got: \${body}") - .delay(simple("2000")) - .setBody(constant("result")) - } - } - } - - @Bean - RouteBuilder clientRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING request \${body}") - .to("direct:receiver") - .log(LoggingLevel.INFO, "test", "RECEIVED response \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy deleted file mode 100644 index e1f5bd587777..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class MulticastConfig { - - @Bean - RouteBuilder firstServiceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:first") - .log(LoggingLevel.INFO, "test", "FIRST request: \${body}") - .delay(simple("1000")) - .setBody(constant("first")) - } - } - } - - @Bean - RouteBuilder secondServiceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:second") - .log(LoggingLevel.INFO, "test", "SECOND request: \${body}") - .delay(simple("2000")) - .setBody(constant("second")) - } - } - } - - @Bean - RouteBuilder clientServiceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING request \${body}") - .multicast() - .parallelProcessing() - .to("direct:first", "direct:second") - .end() - .log(LoggingLevel.INFO, "test", "RECEIVED response \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy deleted file mode 100644 index 69aad50c30c8..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class MulticastDirectCamelTest extends AgentInstrumentationSpecification { - - @Shared - ConfigurableApplicationContext server - - def setupSpec() { - def app = new SpringApplication(MulticastConfig) - server = app.run() - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "parallel multicast to two child services"() { - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - template.sendBody("direct:input", "Example request") - - then: - assertTraces(1) { - trace(0, 3) { - def parent = it - it.span(0) { - name "input" - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://input" - } - } - // there is no strict ordering of "first" and "second" span - def indexOfFirst = span(1).name == "first" ? 1 : 2 - def indexOfSecond = span(1).name == "second" ? 1 : 2 - it.span(indexOfFirst) { - name "first" - kind INTERNAL - parentSpanId parent.span(0).spanId - attributes { - "camel.uri" "direct://first" - } - } - it.span(indexOfSecond) { - name "second" - kind INTERNAL - parentSpanId parent.span(0).spanId - attributes { - "camel.uri" "direct://second" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy deleted file mode 100644 index 9c18ec4832d8..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.api.trace.SpanKind.SERVER - -class RestCamelTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - ConfigurableApplicationContext server - @Shared - int port - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - port = PortUtils.findOpenPort() - def app = new SpringApplication(RestConfig) - app.setDefaultProperties(["restServer.port": port]) - server = app.run() - println getClass().name + " http server started at: http://localhost:$port/" - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "rest component - server and client call with jetty backend"() { - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - // run client and server in separate threads to simulate "real" rest client/server call - new Thread(new Runnable() { - @Override - void run() { - template.sendBodyAndHeaders("direct:start", null, ["module": "firstModule", "unitId": "unitOne"]) - } - } - ).start() - - then: - assertTraces(1) { - trace(0, 5) { - it.span(0) { - name "start" - kind INTERNAL - attributes { - "camel.uri" "direct://start" - } - } - it.span(1) { - name "GET" - kind CLIENT - parentSpanId(span(0).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "rest://get:api/%7Bmodule%7D/unit/%7BunitId%7D" - } - } - it.span(2) { - name "GET /api/{module}/unit/{unitId}" - kind SERVER - parentSpanId(span(1).spanId) - attributes { - "$SemanticAttributes.HTTP_SCHEME" "http" - "$SemanticAttributes.HTTP_TARGET" "/api/firstModule/unit/unitOne" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" String - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_ROUTE" "/api/{module}/unit/{unitId}" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.NET_HOST_NAME" "localhost" - "$SemanticAttributes.NET_HOST_PORT" port - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - "$SemanticAttributes.NET_SOCK_HOST_ADDR" "127.0.0.1" - } - } - it.span(3) { - name "GET /api/{module}/unit/{unitId}" - kind INTERNAL - parentSpanId(span(2).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_URL" "http://localhost:$port/api/firstModule/unit/unitOne" - "camel.uri" String - } - } - it.span(4) { - name "moduleUnit" - kind INTERNAL - parentSpanId(span(3).spanId) - attributes { - "camel.uri" "direct://moduleUnit" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy deleted file mode 100644 index 1345de8616e0..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.model.rest.RestBindingMode -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class RestConfig { - - @Bean - RouteBuilder routes() { - return new RouteBuilder() { - @Override - void configure() throws Exception { - - restConfiguration() - .component("jetty") - .bindingMode(RestBindingMode.auto) - .host("localhost") - .port("{{restServer.port}}") - .producerComponent("http") - - rest("/api") - .get("/{module}/unit/{unitId}") - .to("direct:moduleUnit") - - from("direct:moduleUnit") - .transform().simple("\${header.unitId} of \${header.module}") - - // producer - client route - from("direct:start") - .log(LoggingLevel.INFO, "test", "SENDING request") - .to("rest:get:api/{module}/unit/{unitId}") - .log(LoggingLevel.INFO, "test", "RECEIVED response: '\${body}'") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy deleted file mode 100644 index 16d672899005..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import io.opentelemetry.testing.internal.armeria.client.WebClient -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.SERVER - -class SingleServiceCamelTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - ConfigurableApplicationContext server - @Shared - WebClient client = WebClient.of() - @Shared - int port - @Shared - URI address - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - port = PortUtils.findOpenPort() - address = new URI("http://localhost:$port/") - def app = new SpringApplication(SingleServiceConfig) - app.setDefaultProperties(["camelService.port": port]) - server = app.run() - println getClass().name + " http server started at: http://localhost:$port/" - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "single camel service span"() { - setup: - def requestUrl = address.resolve("/camelService") - - when: - client.post(requestUrl.toString(), "testContent").aggregate().join() - - then: - assertTraces(1) { - trace(0, 1) { - span(0) { - kind SERVER - name "POST /camelService" - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "${address.resolve("/camelService")}" - "camel.uri" "${address.resolve("/camelService")}".replace("localhost", "0.0.0.0") - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy deleted file mode 100644 index 603a4e713ff8..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class SingleServiceConfig { - - @Bean - RouteBuilder serviceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - - from("undertow:http://0.0.0.0:{{camelService.port}}/camelService") - .routeId("camelService") - .streamCaching() - .log("CamelService request: \${body}") - .delay(simple("\${random(1000, 2000)}")) - .transform(simple("CamelService-\${body}")) - .log(LoggingLevel.INFO, "test", "CamelService response: \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy deleted file mode 100644 index 8a979ec9c27f..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class TwoServicesConfig { - - @Bean - RouteBuilder serviceOneRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - - from("undertow:http://0.0.0.0:{{service.one.port}}/serviceOne") - .routeId("serviceOne") - .streamCaching() - .removeHeaders("CamelHttp*") - .log("Service One request: \${body}") - .delay(simple("\${random(1000,2000)}")) - .transform(simple("Service-One-\${body}")) - .to("http://127.0.0.1:{{service.two.port}}/serviceTwo") - .log("Service One response: \${body}") - } - } - } - - @Bean - RouteBuilder serviceTwoRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - - from("jetty:http://0.0.0.0:{{service.two.port}}/serviceTwo?arg=value") - .routeId("serviceTwo") - .streamCaching() - .log("Service Two request: \${body}") - .delay(simple("\${random(1000, 2000)}")) - .transform(simple("Service-Two-\${body}")) - .log("Service Two response: \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy deleted file mode 100644 index 0231b4109d8a..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.impl.DefaultCamelContext -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.api.trace.SpanKind.SERVER - -class TwoServicesWithDirectClientCamelTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - int portOne - @Shared - int portTwo - @Shared - ConfigurableApplicationContext server - @Shared - CamelContext clientContext - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - portOne = PortUtils.findOpenPort() - portTwo = PortUtils.findOpenPort() - def app = new SpringApplication(TwoServicesConfig) - app.setDefaultProperties(["service.one.port": portOne, "service.two.port": portTwo]) - server = app.run() - } - - def createAndStartClient() { - clientContext = new DefaultCamelContext() - clientContext.addRoutes(new RouteBuilder() { - void configure() { - from("direct:input") - .log("SENT Client request") - .to("http://localhost:$portOne/serviceOne") - .log("RECEIVED Client response") - } - }) - clientContext.start() - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "two camel service spans"() { - setup: - createAndStartClient() - ProducerTemplate template = clientContext.createProducerTemplate() - - when: - template.sendBody("direct:input", "Example request") - - then: - assertTraces(1) { - trace(0, 6) { - it.span(0) { - name "input" - kind INTERNAL - attributes { - "camel.uri" "direct://input" - } - } - it.span(1) { - name "POST" - kind CLIENT - parentSpanId(span(0).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://localhost:$portOne/serviceOne" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "http://localhost:$portOne/serviceOne" - } - } - it.span(2) { - name "POST /serviceOne" - kind SERVER - parentSpanId(span(1).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://localhost:$portOne/serviceOne" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "http://0.0.0.0:$portOne/serviceOne" - } - } - it.span(3) { - name "POST" - kind CLIENT - parentSpanId(span(2).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://127.0.0.1:$portTwo/serviceTwo" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "http://127.0.0.1:$portTwo/serviceTwo" - } - } - it.span(4) { - name "POST /serviceTwo" - kind SERVER - parentSpanId(span(3).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.HTTP_SCHEME" "http" - "$SemanticAttributes.HTTP_TARGET" "/serviceTwo" - "$SemanticAttributes.USER_AGENT_ORIGINAL" "Jakarta Commons-HttpClient/3.1" - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" Long - "$SemanticAttributes.HTTP_ROUTE" "/serviceTwo" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.NET_HOST_NAME" "127.0.0.1" - "$SemanticAttributes.NET_HOST_PORT" portTwo - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - } - } - it.span(5) { - name "POST /serviceTwo" - kind INTERNAL - parentSpanId(span(4).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://127.0.0.1:$portTwo/serviceTwo" - "camel.uri" "jetty:http://0.0.0.0:$portTwo/serviceTwo?arg=value" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy deleted file mode 100644 index 519ce2732750..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators - -import com.datastax.driver.core.Cluster -import com.datastax.driver.core.Session -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import org.testcontainers.containers.CassandraContainer -import org.testcontainers.containers.GenericContainer -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class CassandraTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - ConfigurableApplicationContext server - @Shared - GenericContainer cassandra - @Shared - Cluster cluster - @Shared - String host - @Shared - int port - - Session session - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - cassandra = new CassandraContainer() - cassandra.withExposedPorts(9042) - cassandra.start() - - port = cassandra.getFirstMappedPort() - host = cassandra.getHost() - - cluster = cassandra.getCluster() - - def app = new SpringApplication(CassandraConfig) - app.setDefaultProperties(["cassandra.host": host, "cassandra.port": port]) - server = app.run() - } - - def cleanupSpec() { - server?.close() - cluster?.close() - cassandra.stop() - } - - def setup() { - session = cluster.connect() - - session.execute("CREATE KEYSPACE IF NOT EXISTS test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':1};") - session.execute("CREATE TABLE IF NOT EXISTS test.users ( id int primary key, name text );") - session.execute("INSERT INTO test.users (id,name) VALUES (1, 'user1') IF NOT EXISTS;") - session.execute("INSERT INTO test.users (id,name) VALUES (2, 'user2') IF NOT EXISTS;") - } - - def cleanup() { - session?.close() - } - - def "test cassandra "() { - - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - def response = template.requestBody("direct:input", null) - - then: - response.first().getString("name") == "user1" - - assertTraces(1) { - trace(0, 2) { - span(0) { - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://input" - } - } - span(1) { - kind CLIENT - attributes { - "camel.uri" "cql://$host:$port/test" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_STATEMENT" "select * from test.users where id=? ALLOW FILTERING" - "$SemanticAttributes.DB_SYSTEM" "cassandra" - } - } - } - } - - } - -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java new file mode 100644 index 000000000000..758a49963c65 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class DirectCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(DirectConfig.class); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + void simpleDirectToSingleService() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + template.sendBody("direct:input", "Example request"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("input") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasName("receiver") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://receiver")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java new file mode 100644 index 000000000000..1fe96ac8c1ba --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class DirectConfig { + + @Bean + RouteBuilder receiverRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:receiver") + .log(LoggingLevel.INFO, "test", "RECEIVER got: ${body}") + .delay(simple("2000")) + .setBody(constant("result")); + } + }; + } + + @Bean + RouteBuilder clientRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test", "SENDING request ${body}") + .to("direct:receiver") + .log(LoggingLevel.INFO, "test", "RECEIVED response ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java new file mode 100644 index 000000000000..46c9a1bd7e30 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class MulticastConfig { + + @Bean + RouteBuilder firstServiceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:first") + .log(LoggingLevel.INFO, "test", "FIRST request: ${body}") + .delay(simple("1000")) + .setBody(constant("first")); + } + }; + } + + @Bean + RouteBuilder secondServiceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:second") + .log(LoggingLevel.INFO, "test", "SECOND request: ${body}") + .delay(simple("2000")) + .setBody(constant("second")); + } + }; + } + + @Bean + RouteBuilder clientServiceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test", "SENDING request ${body}") + .multicast() + .parallelProcessing() + .to("direct:first", "direct:second") + .end() + .log(LoggingLevel.INFO, "test", "RECEIVED response ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java new file mode 100644 index 000000000000..4584a54eac4b --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class MulticastDirectCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(MulticastConfig.class); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + void parallelMulticastToTwoChildServices() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + template.sendBody("direct:input", "Example request"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactlyInAnyOrder( + span -> + span.hasName("input") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasName("first") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://first")), + span -> + span.hasName("second") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://second")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java new file mode 100644 index 000000000000..7b2ab0904674 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java @@ -0,0 +1,136 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class RestCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(RestConfig.class); + app.setDefaultProperties(ImmutableMap.of("restServer.port", port)); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + void restComponentServerAndClientCallWithJettyBackend() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + // run client and server in separate threads to simulate "real" rest client/server call + new Thread( + () -> + template.sendBodyAndHeaders( + "direct:start", + null, + ImmutableMap.of("module", "firstModule", "unitId", "unitOne"))) + .start(); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("start") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://start")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + stringKey("camel.uri"), + "rest://get:api/%7Bmodule%7D/unit/%7BunitId%7D"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L)), + span -> + span.hasName("GET /api/{module}/unit/{unitId}") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_SCHEME, "http"), + equalTo( + SemanticAttributes.HTTP_TARGET, "/api/firstModule/unit/unitOne"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_ROUTE, "/api/{module}/unit/{unitId}"), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.NET_HOST_NAME, "localhost"), + equalTo(SemanticAttributes.NET_HOST_PORT, Long.valueOf(port)), + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo(SemanticAttributes.NET_SOCK_HOST_ADDR, "127.0.0.1"), + satisfies( + SemanticAttributes.USER_AGENT_ORIGINAL, + val -> val.isInstanceOf(String.class)), + satisfies( + SemanticAttributes.NET_SOCK_PEER_PORT, + val -> val.isInstanceOf(Long.class))), + span -> + span.hasName("GET /api/{module}/unit/{unitId}") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://localhost:" + port + "/api/firstModule/unit/unitOne"), + satisfies( + stringKey("camel.uri"), val -> val.isInstanceOf(String.class))), + span -> + span.hasName("moduleUnit") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://moduleUnit")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java new file mode 100644 index 000000000000..869f8120611d --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.rest.RestBindingMode; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class RestConfig { + + @Bean + RouteBuilder routes() { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + + restConfiguration() + .component("jetty") + .bindingMode(RestBindingMode.auto) + .host("localhost") + .port("{{restServer.port}}") + .producerComponent("http"); + + rest("/api").get("/{module}/unit/{unitId}").to("direct:moduleUnit"); + + from("direct:moduleUnit").transform().simple("${header.unitId} of ${header.module}"); + + // producer - client route + from("direct:start") + .log(LoggingLevel.INFO, "test", "SENDING request") + .to("rest:get:api/{module}/unit/{unitId}") + .log(LoggingLevel.INFO, "test", "RECEIVED response: '${body}'"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java new file mode 100644 index 000000000000..849d5daf1374 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class SingleServiceCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(SingleServiceConfig.class); + app.setDefaultProperties(ImmutableMap.of("camelService.port", port)); + return app.run(); + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + public void singleCamelServiceSpan() { + URI requestUrl = address.resolve("/camelService"); + + client.post(requestUrl.toString(), "testContent").aggregate().join(); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("POST /camelService") + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo(SemanticAttributes.HTTP_URL, requestUrl.toString()), + equalTo( + stringKey("camel.uri"), + requestUrl.toString().replace("localhost", "0.0.0.0"))))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java new file mode 100644 index 000000000000..b862e23a42e4 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class SingleServiceConfig { + + @Bean + RouteBuilder serviceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + + from("undertow:http://0.0.0.0:{{camelService.port}}/camelService") + .routeId("camelService") + .streamCaching() + .log("CamelService request: ${body}") + .delay(simple("${random(1000, 2000)}")) + .transform(simple("CamelService-${body}")) + .log(LoggingLevel.INFO, "test", "CamelService response: ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java new file mode 100644 index 000000000000..f0fe24b4e3bc --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class TwoServicesConfig { + + @Bean + RouteBuilder serviceOneRoute() { + return new RouteBuilder() { + + @Override + public void configure() { + + from("undertow:http://0.0.0.0:{{service.one.port}}/serviceOne") + .routeId("serviceOne") + .streamCaching() + .removeHeaders("CamelHttp*") + .log("Service One request: ${body}") + .delay(simple("${random(1000,2000)}")) + .transform(simple("Service-One-${body}")) + .to("http://127.0.0.1:{{service.two.port}}/serviceTwo") + .log("Service One response: ${body}"); + } + }; + } + + @Bean + RouteBuilder serviceTwoRoute() { + return new RouteBuilder() { + + @Override + public void configure() { + + from("jetty:http://0.0.0.0:{{service.two.port}}/serviceTwo?arg=value") + .routeId("serviceTwo") + .streamCaching() + .log("Service Two request: ${body}") + .delay(simple("${random(1000, 2000)}")) + .transform(simple("Service-Two-${body}")) + .log("Service Two response: ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java new file mode 100644 index 000000000000..b7f379d6397b --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java @@ -0,0 +1,180 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class TwoServicesWithDirectClientCamelTest + extends AbstractHttpServerUsingTest { + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private static CamelContext clientContext; + + private static Integer portOne; + + private static Integer portTwo; + + @Override + protected ConfigurableApplicationContext setupServer() { + portOne = port; + portTwo = PortUtils.findOpenPort(); + SpringApplication app = new SpringApplication(TwoServicesConfig.class); + app.setDefaultProperties( + ImmutableMap.of("service.one.port", portOne, "service.two.port", portTwo)); + return app.run(); + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + void createAndStartClient() throws Exception { + clientContext = new DefaultCamelContext(); + clientContext.addRoutes( + new RouteBuilder() { + @Override + public void configure() { + from("direct:input") + .log("SENT Client request") + .to("http://localhost:" + portOne + "/serviceOne") + .log("RECEIVED Client response"); + } + }); + clientContext.start(); + } + + @Test + void twoCamelServiceSpans() throws Exception { + createAndStartClient(); + + ProducerTemplate template = clientContext.createProducerTemplate(); + + template.sendBody("direct:input", "Example request"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("input") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasName("POST") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://localhost:" + portOne + "/serviceOne"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo( + stringKey("camel.uri"), + "http://localhost:" + portOne + "/serviceOne")), + span -> + span.hasName("POST /serviceOne") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://localhost:" + portOne + "/serviceOne"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo( + stringKey("camel.uri"), + "http://0.0.0.0:" + portOne + "/serviceOne")), + span -> + span.hasName("POST") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(2)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://127.0.0.1:" + portTwo + "/serviceTwo"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo( + stringKey("camel.uri"), + "http://127.0.0.1:" + portTwo + "/serviceTwo")), + span -> + span.hasName("POST /serviceTwo") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_SCHEME, "http"), + equalTo(SemanticAttributes.HTTP_TARGET, "/serviceTwo"), + equalTo( + SemanticAttributes.USER_AGENT_ORIGINAL, + "Jakarta Commons-HttpClient/3.1"), + equalTo(SemanticAttributes.HTTP_ROUTE, "/serviceTwo"), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.NET_HOST_NAME, "127.0.0.1"), + equalTo(SemanticAttributes.NET_HOST_PORT, portTwo), + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + satisfies( + SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, + val -> val.isInstanceOf(Long.class)), + satisfies( + SemanticAttributes.NET_SOCK_PEER_PORT, + val -> val.isInstanceOf(Long.class))), + span -> + span.hasName("POST /serviceTwo") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(4)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://127.0.0.1:" + portTwo + "/serviceTwo"), + equalTo( + stringKey("camel.uri"), + "jetty:http://0.0.0.0:" + portTwo + "/serviceTwo?arg=value")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java similarity index 51% rename from instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy rename to instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java index c88e17a3478b..9b79a0bc7225 100644 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators +package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators; -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; @SpringBootConfiguration @EnableAutoConfiguration @@ -17,13 +17,13 @@ class CassandraConfig { @Bean RouteBuilder serviceRoute() { return new RouteBuilder() { - @Override - void configure() throws Exception { + public void configure() { from("direct:input") - .setHeader("CamelCqlQuery", simple("select * from test.users where id=1 ALLOW FILTERING")) - .toD("cql://{{cassandra.host}}:{{cassandra.port}}/test") + .setHeader( + "CamelCqlQuery", simple("select * from test.users where id=1 ALLOW FILTERING")) + .toD("cql://{{cassandra.host}}:{{cassandra.port}}/test"); } - } + }; } } diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java new file mode 100644 index 000000000000..eeba3d880f42 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java @@ -0,0 +1,125 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +class CassandraTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Container + private static final CassandraContainer cassandra = + new CassandraContainer<>("cassandra:3.11.2").withExposedPorts(9042); + + private static String host; + + private static Integer cassandraPort; + + private static CqlSession cqlSession; + + @Override + protected ConfigurableApplicationContext setupServer() { + cassandra.start(); + cassandraSetup(); + + cassandraPort = cassandra.getFirstMappedPort(); + host = cassandra.getHost(); + + SpringApplication app = new SpringApplication(CassandraConfig.class); + app.setDefaultProperties( + ImmutableMap.of("cassandra.host", host, "cassandra.port", cassandraPort)); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + cqlSession.close(); + cassandra.stop(); + } + + static void cassandraSetup() { + cqlSession = + CqlSession.builder() + .addContactPoint(cassandra.getContactPoint()) + .withLocalDatacenter(cassandra.getLocalDatacenter()) + .build(); + + cqlSession.execute( + "CREATE KEYSPACE IF NOT EXISTS test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"); + cqlSession.execute("CREATE TABLE IF NOT EXISTS test.users (id int PRIMARY KEY, name TEXT);"); + } + + @Test + void testCassandra() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + template.requestBody("direct:input", (Object) null); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo( + stringKey("camel.uri"), + "cql://" + host + ":" + cassandraPort + "/test"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo( + SemanticAttributes.DB_STATEMENT, + "select * from test.users where id=? ALLOW FILTERING"), + equalTo(SemanticAttributes.DB_SYSTEM, "cassandra")))); + } +} From 74380558f97b51f91ccff81758e078f928384ae8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 09:25:23 +0300 Subject: [PATCH 70/95] Bump com.google.protobuf:protobuf-java-util from 3.23.3 to 3.23.4 (#8901) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index bc316046bbb2..bb031eba792b 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { implementation("io.opentelemetry.proto:opentelemetry-proto") implementation("org.testcontainers:testcontainers") implementation("com.fasterxml.jackson.core:jackson-databind") - implementation("com.google.protobuf:protobuf-java-util:3.23.3") + implementation("com.google.protobuf:protobuf-java-util:3.23.4") implementation("io.grpc:grpc-netty-shaded") implementation("io.grpc:grpc-protobuf") implementation("io.grpc:grpc-stub") From ff9567c2e3417919726e6e371f48bbf3acc96a4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 09:25:50 +0300 Subject: [PATCH 71/95] Bump dockerJavaVersion from 3.3.1 to 3.3.2 (#8900) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smoke-tests/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index bb031eba792b..03ae42870ce9 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -15,7 +15,7 @@ otelJava { maxJavaVersionForTests.set(JavaVersion.VERSION_11) } -val dockerJavaVersion = "3.3.1" +val dockerJavaVersion = "3.3.2" dependencies { testCompileOnly("com.google.auto.value:auto-value-annotations") testAnnotationProcessor("com.google.auto.value:auto-value") From 9498cfbb36195a55c2f9300f38bd5dd86a63fc4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 09:27:19 +0300 Subject: [PATCH 72/95] Bump com.google.protobuf:protobuf-java-util from 3.23.3 to 3.23.4 in /examples/extension (#8903) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/extension/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extension/build.gradle b/examples/extension/build.gradle index c171b162a850..ca3a89090b74 100644 --- a/examples/extension/build.gradle +++ b/examples/extension/build.gradle @@ -101,7 +101,7 @@ dependencies { //All dependencies below are only for tests testImplementation("org.testcontainers:testcontainers:1.18.3") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") - testImplementation("com.google.protobuf:protobuf-java-util:3.23.3") + testImplementation("com.google.protobuf:protobuf-java-util:3.23.4") testImplementation("com.squareup.okhttp3:okhttp:4.11.0") testImplementation("io.opentelemetry:opentelemetry-api") testImplementation("io.opentelemetry.proto:opentelemetry-proto:0.20.0-alpha") From 658801a133304f08093031129a0afc1efbf07210 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 09:27:46 +0300 Subject: [PATCH 73/95] Bump com.google.protobuf:protobuf-java-util from 3.23.3 to 3.23.4 in /examples/distro (#8905) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/distro/smoke-tests/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/distro/smoke-tests/build.gradle b/examples/distro/smoke-tests/build.gradle index d65da259ebbf..f311150d306b 100644 --- a/examples/distro/smoke-tests/build.gradle +++ b/examples/distro/smoke-tests/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { testImplementation("org.testcontainers:testcontainers:1.18.3") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") - testImplementation("com.google.protobuf:protobuf-java-util:3.23.3") + testImplementation("com.google.protobuf:protobuf-java-util:3.23.4") testImplementation("com.squareup.okhttp3:okhttp:4.11.0") testImplementation("io.opentelemetry.proto:opentelemetry-proto:0.20.0-alpha") testImplementation("io.opentelemetry:opentelemetry-api:${versions.opentelemetry}") From 9c48f964595cd7aadeaff577111fabc23f6d0ed4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 15:04:28 +0300 Subject: [PATCH 74/95] Bump io.opentelemetry:opentelemetry-bom from 1.27.0 to 1.28.0 in /examples/distro (#8904) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/distro/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index abb4423d7b36..bfafc945e327 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -27,7 +27,7 @@ subprojects { ext { versions = [ // this line is managed by .github/scripts/update-sdk-version.sh - opentelemetrySdk : "1.27.0", + opentelemetrySdk : "1.28.0", // these lines are managed by .github/scripts/update-version.sh opentelemetryJavaagent : "1.28.0-SNAPSHOT", From 87a8fb474b726009aa4dd599d5fdb38cec05c399 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 15:04:43 +0300 Subject: [PATCH 75/95] Bump io.opentelemetry:opentelemetry-bom from 1.27.0 to 1.28.0 in /examples/extension (#8902) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/extension/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extension/build.gradle b/examples/extension/build.gradle index ca3a89090b74..eb0bd34f3363 100644 --- a/examples/extension/build.gradle +++ b/examples/extension/build.gradle @@ -23,7 +23,7 @@ version '1.0' ext { versions = [ // this line is managed by .github/scripts/update-sdk-version.sh - opentelemetrySdk : "1.27.0", + opentelemetrySdk : "1.28.0", // these lines are managed by .github/scripts/update-version.sh opentelemetryJavaagent : "1.28.0-SNAPSHOT", From 0607f0b6b8aa688598e3b4eb51deb4b2dcef50c4 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Sat, 8 Jul 2023 15:05:24 +0300 Subject: [PATCH 76/95] Fix NPE in aws instrumentation on duplicate TracingExecutionInterceptor (#8896) --- .../v2_2/TracingExecutionInterceptor.java | 7 +++-- .../awssdk/v2_2/Aws2SqsTracingTest.groovy | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index d60a29bf812a..b5850350f6ea 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -230,8 +230,11 @@ private void populateRequestAttributes( public void afterExecution( Context.AfterExecution context, ExecutionAttributes executionAttributes) { - // Other special handling could be shortcut-&&ed after this (false is returned if not handled). - SqsAccess.afterReceiveMessageExecution(context, executionAttributes, this); + if (executionAttributes.getAttribute(SDK_HTTP_REQUEST_ATTRIBUTE) != null) { + // Other special handling could be shortcut-&&ed after this (false is returned if not + // handled). + SqsAccess.afterReceiveMessageExecution(context, executionAttributes, this); + } io.opentelemetry.context.Context otelContext = getContext(executionAttributes); if (otelContext != null) { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy index 236f846fa85f..c85cb4d90cce 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy @@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 import io.opentelemetry.instrumentation.test.LibraryTestTrait import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.services.sqs.SqsClient class Aws2SqsTracingTest extends AbstractAws2SqsTracingTest implements LibraryTestTrait { @Override @@ -23,4 +24,30 @@ class Aws2SqsTracingTest extends AbstractAws2SqsTracingTest implements LibraryTe boolean isSqsAttributeInjectionEnabled() { false } + + def "duplicate tracing interceptor"() { + setup: + def builder = SqsClient.builder() + configureSdkClient(builder) + def telemetry = AwsSdkTelemetry.builder(getOpenTelemetry()) + .setCaptureExperimentalSpanAttributes(true) + .build() + def overrideConfiguration = ClientOverrideConfiguration.builder() + .addExecutionInterceptor(telemetry.newExecutionInterceptor()) + .addExecutionInterceptor(telemetry.newExecutionInterceptor()) + .build() + builder.overrideConfiguration(overrideConfiguration) + def client = builder.build() + + client.createQueue(createQueueRequest) + + when: + client.sendMessage(sendMessageRequest) + + def resp = client.receiveMessage(receiveMessageRequest) + + then: + resp.messages().size() == 1 + assertSqsTraces() + } } From 6f09cf79b1a18850e88897a54c1e39ef167b803a Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Sun, 9 Jul 2023 18:49:32 +0300 Subject: [PATCH 77/95] Unpin markdown-link-check version (#8899) --- .github/workflows/reusable-markdown-link-check.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/reusable-markdown-link-check.yml b/.github/workflows/reusable-markdown-link-check.yml index d39a6e225ef6..2832088d4305 100644 --- a/.github/workflows/reusable-markdown-link-check.yml +++ b/.github/workflows/reusable-markdown-link-check.yml @@ -10,9 +10,7 @@ jobs: - uses: actions/checkout@v3 - name: Install markdown-link-check - # fix version to 3.10.3 as 3.11.0 appears to ignore --config, so we can't specify our own - # configuration file - run: npm install -g markdown-link-check@3.10.3 + run: npm install -g markdown-link-check - name: Run markdown-link-check run: | From acbab58a4696169802595e75e738572685abad0c Mon Sep 17 00:00:00 2001 From: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Date: Mon, 10 Jul 2023 12:45:01 -0700 Subject: [PATCH 78/95] Update the OpenTelemetry SDK version to 1.28.0 (#8897) Co-authored-by: Lauri Tulmin --- dependencyManagement/build.gradle.kts | 2 +- .../jmx/JmxMetricInsightInstaller.java | 3 +- .../oshi/OshiMetricsInstaller.java | 3 +- .../java17/Java17RuntimeMetricsInstaller.java | 3 +- .../java8/Java8RuntimeMetricsInstaller.java | 3 +- .../JaegerSpanExporterAutoConfiguration.java | 16 ++-- .../jaeger/JaegerSpanExporterProperties.java | 1 + ...egerSpanExporterAutoConfigurationTest.java | 17 +++- .../javaagent/tooling/AgentInstaller.java | 6 +- .../tooling/OpenTelemetryInstaller.java | 2 +- .../ConfigurationPropertiesSupplierTest.java | 4 +- licenses/licenses.md | 94 ++++++++++--------- 12 files changed, 89 insertions(+), 65 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7723de7a2756..39d3c109c2c5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -12,7 +12,7 @@ val dependencyVersions = hashMapOf() rootProject.extra["versions"] = dependencyVersions // this line is managed by .github/scripts/update-sdk-version.sh -val otelSdkVersion = "1.27.0" +val otelSdkVersion = "1.28.0" val otelSdkAlphaVersion = otelSdkVersion.replaceFirst("(-SNAPSHOT)?$".toRegex(), "-alpha$1") // Need both BOM and groovy jars diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index 17687fec57ba..69b3c09c90d7 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.jmx.yaml.RuleParser; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.io.InputStream; import java.nio.file.Files; @@ -27,7 +28,7 @@ public class JmxMetricInsightInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); if (config.getBoolean("otel.jmx.enabled", true)) { JmxMetricInsight service = diff --git a/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java b/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java index 0496c39e8122..5a5b04443d2e 100644 --- a/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java +++ b/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java @@ -8,6 +8,7 @@ import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.lang.reflect.Method; @@ -20,7 +21,7 @@ public class OshiMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); if (!config.getBoolean("otel.instrumentation.oshi.enabled", defaultEnabled)) { diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java index d54350d1102a..efe12ae65c58 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; /** An {@link AgentListener} that enables runtime metrics during agent startup. */ @@ -19,7 +20,7 @@ public class Java17RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); RuntimeMetrics runtimeMetrics = null; diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java index 922db7312eaf..83f895a7b508 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.ArrayList; import java.util.List; @@ -27,7 +28,7 @@ public class Java8RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java index dc017ff3396b..79b426d68452 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java @@ -5,8 +5,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.jaeger; -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -17,22 +15,26 @@ import org.springframework.context.annotation.Configuration; /** - * Configures {@link JaegerGrpcSpanExporter} for tracing. + * Configures {@link io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter} for tracing. * - *

Initializes {@link JaegerGrpcSpanExporter} bean if bean is missing. + *

Initializes {@link io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter} bean if bean is + * missing. */ @Configuration @AutoConfigureBefore(OpenTelemetryAutoConfiguration.class) @EnableConfigurationProperties(JaegerSpanExporterProperties.class) @ConditionalOnProperty(prefix = "otel.exporter.jaeger", name = "enabled", matchIfMissing = true) -@ConditionalOnClass(JaegerGrpcSpanExporter.class) +@ConditionalOnClass(io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class) +@Deprecated public class JaegerSpanExporterAutoConfiguration { @Bean @ConditionalOnMissingBean - public JaegerGrpcSpanExporter otelJaegerSpanExporter(JaegerSpanExporterProperties properties) { + public io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter otelJaegerSpanExporter( + JaegerSpanExporterProperties properties) { - JaegerGrpcSpanExporterBuilder builder = JaegerGrpcSpanExporter.builder(); + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder builder = + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.builder(); if (properties.getEndpoint() != null) { builder.setEndpoint(properties.getEndpoint()); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java index 9c9c0baf3b88..fa05b750d63e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java @@ -19,6 +19,7 @@ *

Get max wait time for Collector to process Span Batches */ @ConfigurationProperties(prefix = "otel.exporter.jaeger") +@Deprecated public final class JaegerSpanExporterProperties { private boolean enabled = true; diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java index b46645121c17..7f48a09484cc 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -16,7 +15,11 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -/** Spring Boot auto configuration test for {@link JaegerGrpcSpanExporter}. */ +/** + * Spring Boot auto configuration test for {@link + * io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter}. + */ +@SuppressWarnings("deprecation") class JaegerSpanExporterAutoConfigurationTest { private final ApplicationContextRunner contextRunner = @@ -37,7 +40,10 @@ void exportersEnabled() { .withPropertyValues("otel.exporter.jaeger.enabled=true") .run( context -> - assertThat(context.getBean("otelJaegerSpanExporter", JaegerGrpcSpanExporter.class)) + assertThat( + context.getBean( + "otelJaegerSpanExporter", + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class)) .isNotNull()); } @@ -74,7 +80,10 @@ void disabledProperty() { void noProperty() { this.contextRunner.run( context -> - assertThat(context.getBean("otelJaegerSpanExporter", JaegerGrpcSpanExporter.class)) + assertThat( + context.getBean( + "otelJaegerSpanExporter", + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class)) .isNotNull()); } } diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index ef83c3879e52..eafe2dd14b52 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -41,6 +41,7 @@ import io.opentelemetry.javaagent.tooling.muzzle.AgentTooling; import io.opentelemetry.javaagent.tooling.util.Trie; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.lang.instrument.Instrumentation; import java.util.ArrayList; @@ -119,7 +120,7 @@ private static void installBytebuddyAgent( AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = installOpenTelemetrySdk(extensionClassLoader); - ConfigProperties sdkConfig = autoConfiguredSdk.getConfig(); + ConfigProperties sdkConfig = AutoConfigureUtil.getConfig(autoConfiguredSdk); InstrumentationConfig.internalInitializeConfig(new ConfigPropertiesBridge(sdkConfig)); copyNecessaryConfigToSystemProperties(sdkConfig); @@ -286,7 +287,8 @@ private static void runAfterAgentListeners( // the application is already setting the global LogManager and AgentListener won't be able // to touch it due to class loader locking. boolean shouldForceSynchronousAgentListenersCalls = - autoConfiguredSdk.getConfig().getBoolean(FORCE_SYNCHRONOUS_AGENT_LISTENERS_CONFIG, false); + AutoConfigureUtil.getConfig(autoConfiguredSdk) + .getBoolean(FORCE_SYNCHRONOUS_AGENT_LISTENERS_CONFIG, false); boolean javaBefore9 = isJavaBefore9(); if (!shouldForceSynchronousAgentListenersCalls && javaBefore9 && isAppUsingCustomLogManager()) { logger.fine("Custom JUL LogManager detected: delaying AgentListener#afterAgent() calls"); diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java index 9b185d45dcd8..c59d0e146101 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java @@ -26,7 +26,7 @@ public static AutoConfiguredOpenTelemetrySdk installOpenTelemetrySdk( AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = AutoConfiguredOpenTelemetrySdk.builder() - .setResultAsGlobal(true) + .setResultAsGlobal() .setServiceClassLoader(extensionClassLoader) // disable the logs exporter by default for the time being // FIXME remove this in the 2.x branch diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java index 5bc5af79faec..a1280a91b6c2 100644 --- a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java @@ -13,6 +13,7 @@ import io.opentelemetry.api.events.GlobalEventEmitterProvider; import io.opentelemetry.javaagent.tooling.OpenTelemetryInstaller; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import java.io.IOException; @@ -49,7 +50,8 @@ void fileConfigOverwritesUserPropertiesSupplier(@TempDir Path tempDir) throws IO OpenTelemetryInstaller.installOpenTelemetrySdk(this.getClass().getClassLoader()); // then - assertThat(autoConfiguredSdk.getConfig().getString("custom.key")).isEqualTo("42"); + assertThat(AutoConfigureUtil.getConfig(autoConfiguredSdk).getString("custom.key")) + .isEqualTo("42"); } // SPI used in test diff --git a/licenses/licenses.md b/licenses/licenses.md index 6b5c7401c11e..50d87eaa4583 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -1,7 +1,7 @@ # javaagent ## Dependency License Report -_2023-06-17 09:51:16 UTC_ +_2023-07-07 18:51:13 EEST_ ## Apache License, Version 2.0 **1** **Group:** `com.blogspot.mydailyjava` **Name:** `weak-lock-free` **Version:** `0.18` @@ -37,204 +37,208 @@ _2023-06-17 09:51:16 UTC_ > - **POM Project URL**: [https://github.com/square/okio/](https://github.com/square/okio/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**7** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.27.0` +**7** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**8** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-events` **Version:** `1.27.0-alpha` +**8** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-events` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**9** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.27.0` +**9** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**10** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-common` **Version:** `1.27.0` +**10** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-common` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**11** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-jaeger` **Version:** `1.27.0` +**11** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-jaeger` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**12** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging` **Version:** `1.27.0` +**12** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**13** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging-otlp` **Version:** `1.27.0` +**13** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging-otlp` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**14** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp` **Version:** `1.27.0` +**14** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**15** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp-common` **Version:** `1.27.0` +**15** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp-common` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**16** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-prometheus` **Version:** `1.27.0-alpha` +**16** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-prometheus` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**17** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-zipkin` **Version:** `1.27.0` +**17** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-sender-okhttp` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**18** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-incubator` **Version:** `1.27.0-alpha` +**18** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-zipkin` **Version:** `1.28.0` +> - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +**19** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-incubator` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**19** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-kotlin` **Version:** `1.27.0` +**20** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-kotlin` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**20** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.27.0` +**21** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**21** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.27.0` +**22** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**22** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.27.0` +**23** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**23** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure` **Version:** `1.27.0-alpha` +**24** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**24** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure-spi` **Version:** `1.27.0` +**25** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure-spi` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**25** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-incubator` **Version:** `1.27.0-alpha` +**26** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-incubator` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**26** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-jaeger-remote-sampler` **Version:** `1.27.0` +**27** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-jaeger-remote-sampler` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**27** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.27.0` +**28** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**28** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.27.0` +**29** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**29** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.27.0` +**30** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**30** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.27.0-alpha` +**31** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**31** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray-propagator` **Version:** `1.27.0-alpha` +**32** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray-propagator` **Version:** `1.27.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**32** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-reporter` **Version:** `2.16.3` +**33** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-reporter` **Version:** `2.16.3` > - **Manifest Project URL**: [https://zipkin.io/](https://zipkin.io/) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [zipkin-reporter-2.16.3.jar/META-INF/LICENSE](zipkin-reporter-2.16.3.jar/META-INF/LICENSE) -**33** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-sender-okhttp3` **Version:** `2.16.3` +**34** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-sender-okhttp3` **Version:** `2.16.3` > - **Manifest Project URL**: [https://zipkin.io/](https://zipkin.io/) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [zipkin-sender-okhttp3-2.16.3.jar/META-INF/LICENSE](zipkin-sender-okhttp3-2.16.3.jar/META-INF/LICENSE) -**34** **Group:** `io.zipkin.zipkin2` **Name:** `zipkin` **Version:** `2.23.2` +**35** **Group:** `io.zipkin.zipkin2` **Name:** `zipkin` **Version:** `2.23.2` > - **Manifest Project URL**: [http://zipkin.io/](http://zipkin.io/) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [zipkin-2.23.2.jar/META-INF/LICENSE](zipkin-2.23.2.jar/META-INF/LICENSE) -**35** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.14.5` +**36** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.14.5` > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [byte-buddy-dep-1.14.5.jar/META-INF/LICENSE](byte-buddy-dep-1.14.5.jar/META-INF/LICENSE) - [byte-buddy-dep-1.14.5.jar/META-INF/NOTICE](byte-buddy-dep-1.14.5.jar/META-INF/NOTICE) -**36** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` +**37** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` > - **POM Project URL**: [http://www.jetbrains.org](http://www.jetbrains.org) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**37** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.8.22` +**38** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**38** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.8.22` +**39** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**39** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.8.22` +**40** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**40** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.8.22` +**41** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**41** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` +**42** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**42** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` +**43** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**43** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` +**44** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**44** **Group:** `org.snakeyaml` **Name:** `snakeyaml-engine` **Version:** `2.6` +**45** **Group:** `org.snakeyaml` **Name:** `snakeyaml-engine` **Version:** `2.6` > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM Project URL**: [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) ## MIT License -**45** **Group:** `org.slf4j` **Name:** `slf4j-api` **Version:** `2.0.7` +**46** **Group:** `org.slf4j` **Name:** `slf4j-api` **Version:** `2.0.7` > - **Project URL**: [http://www.slf4j.org](http://www.slf4j.org) > - **POM License**: MIT License - [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) > - **Embedded license files**: [slf4j-api-2.0.7.jar/META-INF/LICENSE.txt](slf4j-api-2.0.7.jar/META-INF/LICENSE.txt) -**46** **Group:** `org.slf4j` **Name:** `slf4j-simple` **Version:** `2.0.7` +**47** **Group:** `org.slf4j` **Name:** `slf4j-simple` **Version:** `2.0.7` > - **Project URL**: [http://www.slf4j.org](http://www.slf4j.org) > - **POM License**: MIT License - [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) > - **Embedded license files**: [slf4j-simple-2.0.7.jar/META-INF/LICENSE.txt](slf4j-simple-2.0.7.jar/META-INF/LICENSE.txt) ## The 3-Clause BSD License -**47** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` +**48** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**48** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` +**49** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**49** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` +**50** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) @@ -243,4 +247,4 @@ _2023-06-17 09:51:16 UTC_ ## Unknown -**50** **Group:** `com.squareup.okio` **Name:** `okio` **Version:** `3.2.0` +**51** **Group:** `com.squareup.okio` **Name:** `okio` **Version:** `3.2.0` From b3932c7089dc07b815063ff4ae78db11fa8b3a8a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 11 Jul 2023 03:50:14 +0200 Subject: [PATCH 79/95] (micrometer) don't add . to empty unit with prometheus naming conventions (#8872) --- .../micrometer/v1_5/PrometheusModeNamingConvention.java | 2 +- .../micrometer/v1_5/AbstractPrometheusModeTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java index 2a4143512756..f876ddb7d111 100644 --- a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java @@ -20,7 +20,7 @@ public String name(String name, Meter.Type type, @Nullable String baseUnit) { if (type == Meter.Type.COUNTER || type == Meter.Type.DISTRIBUTION_SUMMARY || type == Meter.Type.GAUGE) { - if (baseUnit != null && !name.endsWith("." + baseUnit)) { + if (baseUnit != null && !baseUnit.equals("") && !name.endsWith("." + baseUnit)) { name = name + "." + baseUnit; } } diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java index 2666a1308812..65f3ea93e943 100644 --- a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java @@ -34,7 +34,7 @@ void testCounter() { Counter.builder("testPrometheusCounter") .description("This is a test counter") .tags("tag", "value") - .baseUnit("items") + .baseUnit("") .register(Metrics.globalRegistry); // when @@ -44,13 +44,13 @@ void testCounter() { testing() .waitAndAssertMetrics( INSTRUMENTATION_NAME, - "testPrometheusCounter.items", + "testPrometheusCounter", metrics -> metrics.anySatisfy( metric -> assertThat(metric) .hasDescription("This is a test counter") - .hasUnit("items") + .hasUnit("") .hasDoubleSumSatisfying( sum -> sum.isMonotonic() From 9b0f575814e536cbda7b6a6a5f047d9a90ac8777 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:32:18 +0300 Subject: [PATCH 80/95] Bump org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.6.0 (#8914) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle-plugins/settings.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/settings.gradle.kts b/gradle-plugins/settings.gradle.kts index 64dafcc81018..7690fa36a083 100644 --- a/gradle-plugins/settings.gradle.kts +++ b/gradle-plugins/settings.gradle.kts @@ -6,5 +6,5 @@ pluginManagement { } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 4e3f6023c200..d17a6dee9f26 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ pluginManagement { plugins { id("com.gradle.enterprise") version "3.13.4" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11" - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0" } dependencyResolutionManagement { From 164358d78827e9b0e8d75a5e38b7f7c9a80571f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:39:00 +0300 Subject: [PATCH 81/95] Update Gradle Wrapper to 8.2.1 (#8918) Signed-off-by: gradle-update-robot Co-authored-by: gradle-update-robot --- benchmark-overhead/gradle/wrapper/gradle-wrapper.properties | 4 ++-- examples/distro/gradle/wrapper/gradle-wrapper.properties | 4 ++-- examples/extension/gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradle-plugins/gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties b/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties index a7c2bd1bdc63..c2447881d454 100644 --- a/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties +++ b/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/examples/distro/gradle/wrapper/gradle-wrapper.properties b/examples/distro/gradle/wrapper/gradle-wrapper.properties index a7c2bd1bdc63..c2447881d454 100644 --- a/examples/distro/gradle/wrapper/gradle-wrapper.properties +++ b/examples/distro/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/examples/extension/gradle/wrapper/gradle-wrapper.properties b/examples/extension/gradle/wrapper/gradle-wrapper.properties index a7c2bd1bdc63..c2447881d454 100644 --- a/examples/extension/gradle/wrapper/gradle-wrapper.properties +++ b/examples/extension/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradle-plugins/gradle/wrapper/gradle-wrapper.properties b/gradle-plugins/gradle/wrapper/gradle-wrapper.properties index a7c2bd1bdc63..c2447881d454 100644 --- a/gradle-plugins/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugins/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a7c2bd1bdc63..c2447881d454 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 8011cb4af2fbea093ea6bba4375d81e25a494841 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 11 Jul 2023 14:05:10 +0300 Subject: [PATCH 82/95] Set server span name for aborted requests in quarkus resteasy native (#8891) Co-authored-by: Mateusz Rzeszutek --- .../reactive/AbstractQuarkusJaxRsTest.java | 21 ++++++++ ...esteasyReactiveContextInstrumentation.java | 51 +++++++++++++++++++ .../InvocationHandlerInstrumentation.java | 7 +-- .../resteasy/reactive/OtelRequestContext.java | 43 ++++++++++++++++ ...ResteasyReactiveInstrumentationModule.java | 6 ++- .../reactive/ResteasyReactiveSpanName.java | 9 ++-- .../resteasy/reactive/v2_0/TestFilter.java | 27 ++++++++++ .../resteasy/reactive/v3_0/TestFilter.java | 27 ++++++++++ 8 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java create mode 100644 instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java create mode 100644 instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java create mode 100644 instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java diff --git a/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java b/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java index 058a2c3e1608..e41bc99d4923 100644 --- a/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java +++ b/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java @@ -17,6 +17,8 @@ import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpMethod; import java.time.Duration; +import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -42,8 +44,15 @@ static void setUp() { } private static AggregatedHttpResponse request(String path) { + return request(path, Collections.emptyMap()); + } + + private static AggregatedHttpResponse request(String path, Map headers) { AggregatedHttpRequest request = AggregatedHttpRequest.of(HttpMethod.GET, "h1c://localhost:" + port + path); + if (!headers.isEmpty()) { + request = AggregatedHttpRequest.of(request.headers().toBuilder().add(headers).build()); + } return client.execute(request).aggregate().join(); } @@ -96,4 +105,16 @@ void testSubResourceLocator() { span.hasName("GET /test-sub-resource-locator/call/sub") .hasKind(SpanKind.SERVER))); } + + @Test + void testAbort() { + AggregatedHttpResponse response = request("/hello", Collections.singletonMap("abort", "true")); + assertThat(response.status().code()).isEqualTo(401); + assertThat(response.contentUtf8()).isEqualTo("Aborted"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("GET /hello").hasKind(SpanKind.SERVER))); + } } diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java new file mode 100644 index 000000000000..14959ea11bd5 --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.quarkus.resteasy.reactive; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; + +public class AbstractResteasyReactiveContextInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("run"), + AbstractResteasyReactiveContextInstrumentation.class.getName() + "$RunAdvice"); + } + + @SuppressWarnings("unused") + public static class RunAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static OtelRequestContext onEnter( + @Advice.This AbstractResteasyReactiveContext requestContext) { + if (requestContext instanceof ResteasyReactiveRequestContext) { + ResteasyReactiveRequestContext context = (ResteasyReactiveRequestContext) requestContext; + return OtelRequestContext.start(context); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.Enter OtelRequestContext context) { + if (context != null) { + context.close(); + } + } + } +} diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java index 204ac9eee860..19025aa666f7 100644 --- a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java @@ -7,7 +7,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named; -import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; @@ -31,10 +30,8 @@ public void transform(TypeTransformer transformer) { public static class HandleAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) ResteasyReactiveRequestContext requestContext, - @Advice.Local("otelScope") Scope scope) { - ResteasyReactiveSpanName.INSTANCE.updateServerSpanName(requestContext); + public static void onEnter(@Advice.Argument(0) ResteasyReactiveRequestContext requestContext) { + OtelRequestContext.onInvoke(requestContext); } } } diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java new file mode 100644 index 000000000000..bf22d536941f --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.quarkus.resteasy.reactive; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; + +public final class OtelRequestContext { + private static final ThreadLocal contextThreadLocal = new ThreadLocal<>(); + private boolean firstInvoke = true; + + public static OtelRequestContext start(ResteasyReactiveRequestContext requestContext) { + OtelRequestContext context = new OtelRequestContext(); + contextThreadLocal.set(context); + ResteasyReactiveSpanName.INSTANCE.updateServerSpanName( + requestContext, HttpRouteSource.CONTROLLER); + return context; + } + + public static void onInvoke(ResteasyReactiveRequestContext requestContext) { + OtelRequestContext context = contextThreadLocal.get(); + if (context == null) { + return; + } + // we ignore the first invoke as it uses the same context that we get in start, the second etc. + // invoke will be for sub resource locator that changes the path + if (context.firstInvoke) { + context.firstInvoke = false; + return; + } + ResteasyReactiveSpanName.INSTANCE.updateServerSpanName( + requestContext, HttpRouteSource.NESTED_CONTROLLER); + } + + public void close() { + contextThreadLocal.remove(); + } + + private OtelRequestContext() {} +} diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java index ecc32a279d10..1a1be882bde4 100644 --- a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.quarkus.resteasy.reactive; -import static java.util.Collections.singletonList; +import static java.util.Arrays.asList; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; @@ -21,6 +21,8 @@ public QuarkusResteasyReactiveInstrumentationModule() { @Override public List typeInstrumentations() { - return singletonList(new InvocationHandlerInstrumentation()); + return asList( + new AbstractResteasyReactiveContextInstrumentation(), + new InvocationHandlerInstrumentation()); } } diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java index d1e47490fe56..1f14607d89cf 100644 --- a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java @@ -15,22 +15,25 @@ import org.jboss.resteasy.reactive.server.mapping.RuntimeResource; import org.jboss.resteasy.reactive.server.mapping.URITemplate; -public final class ResteasyReactiveSpanName { +final class ResteasyReactiveSpanName { // remember previous path to handle sub path locators private static final VirtualField pathField = VirtualField.find(ResteasyReactiveRequestContext.class, String.class); public static final ResteasyReactiveSpanName INSTANCE = new ResteasyReactiveSpanName(); - public void updateServerSpanName(ResteasyReactiveRequestContext requestContext) { + void updateServerSpanName(ResteasyReactiveRequestContext requestContext, HttpRouteSource source) { Context context = Context.current(); String jaxRsName = calculateJaxRsName(requestContext); - HttpRouteHolder.updateHttpRoute(context, HttpRouteSource.NESTED_CONTROLLER, jaxRsName); + HttpRouteHolder.updateHttpRoute(context, source, jaxRsName); pathField.set(requestContext, jaxRsName); } private static String calculateJaxRsName(ResteasyReactiveRequestContext requestContext) { RuntimeResource target = requestContext.getTarget(); + if (target == null) { + return null; + } URITemplate classPath = target.getClassPath(); URITemplate path = target.getPath(); String name = normalize(classPath) + normalize(path); diff --git a/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java b/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java new file mode 100644 index 000000000000..01696d6fb74a --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.quarkus.resteasy.reactive.v2_0; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext containerRequestContext) { + if (containerRequestContext.getHeaderString("abort") != null) { + containerRequestContext.abortWith( + Response.status(Response.Status.UNAUTHORIZED) + .entity("Aborted") + .type(MediaType.TEXT_PLAIN_TYPE) + .build()); + } + } +} diff --git a/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java b/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java new file mode 100644 index 000000000000..2ab6db124779 --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.quarkus.resteasy.reactive.v3_0; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class TestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext containerRequestContext) { + if (containerRequestContext.getHeaderString("abort") != null) { + containerRequestContext.abortWith( + Response.status(Response.Status.UNAUTHORIZED) + .entity("Aborted") + .type(MediaType.TEXT_PLAIN_TYPE) + .build()); + } + } +} From 8815952300286fb280e152a3a7e2f93f4a421de8 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Tue, 11 Jul 2023 17:47:01 +0200 Subject: [PATCH 83/95] Initialize appenders in the spring boot starter (#8888) Co-authored-by: Lauri Tulmin --- .../build.gradle.kts | 67 +++++++++++--- .../OpenTelemetryAutoConfiguration.java | 4 - ...penTelemetryAppenderAutoConfiguration.java | 61 +++++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../OpenTelemetryAutoConfigurationTest.java | 7 -- .../TraceAspectAutoConfigurationTest.java | 7 -- ...egerSpanExporterAutoConfigurationTest.java | 7 -- ...ngMetricExporterAutoConfigurationTest.java | 7 -- ...gingSpanExporterAutoConfigurationTest.java | 7 -- .../OtlpLogExporterAutoConfigurationTest.java | 7 -- ...lpMetricExporterAutoConfigurationTest.java | 7 -- ...OtlpSpanExporterAutoConfigurationTest.java | 7 -- ...pkinSpanExporterAutoConfigurationTest.java | 7 -- .../RestTemplateAutoConfigurationTest.java | 7 -- .../WebClientAutoConfigurationTest.java | 7 -- .../MicrometerShimAutoConfigurationTest.java | 7 -- .../PropagationAutoConfigurationTest.java | 7 -- .../PropagationPropertiesTest.java | 7 -- .../OtelResourceAutoConfigurationTest.java | 7 -- .../resources/OtelResourcePropertiesTest.java | 7 -- .../SpringResourceConfigPropertiesTest.java | 7 -- ...MvcFilterAutoConfigurationSpring6Test.java | 2 - .../WebMvcFilterAutoConfigurationTest.java | 2 - .../logging/LogbackAppenderTest.java | 87 +++++++++++++++++++ .../resources/logback-test.xml | 19 ++++ .../testing/LibraryTestRunner.java | 14 ++- 27 files changed, 233 insertions(+), 144 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 8cd1255fd92c..ce279dbc5159 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -41,6 +41,10 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") compileOnly("io.opentelemetry:opentelemetry-exporter-zipkin") compileOnly(project(":instrumentation-annotations")) + compileOnly(project(":instrumentation:log4j:log4j-appender-2.17:library")) + compileOnly("org.apache.logging.log4j:log4j-core:2.17.0") + compileOnly(project(":instrumentation:logback:logback-appender-1.0:library")) + compileOnly("ch.qos.logback:logback-classic:1.0.0") compileOnly(project(":instrumentation:resources:library")) annotationProcessor("com.google.auto.service:auto-service") @@ -77,24 +81,59 @@ if (latestDepTest) { } } -tasks.compileTestJava { - options.compilerArgs.add("-parameters") +testing { + suites { + val testLogbackAppender by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation(project(":testing-common")) + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") + + implementation(project(":instrumentation:logback:logback-appender-1.0:library")) + // using the same versions as in the spring-boot-autoconfigure + implementation("ch.qos.logback:logback-classic") { + version { + strictly("1.2.11") + } + } + implementation("org.slf4j:slf4j-api") { + version { + strictly("1.7.32") + } + } + } + } + } } -tasks.withType().configureEach { - usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) +tasks { + check { + dependsOn(testing.suites) + } + + compileTestJava { + options.compilerArgs.add("-parameters") + } + + test { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } - systemProperty("testLatestDeps", latestDepTest) + withType().configureEach { + systemProperty("testLatestDeps", latestDepTest) - // required on jdk17 - jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") - jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") - // disable tests on openj9 18 because they often crash JIT compiler - val testJavaVersion = gradle.startParameter.projectProperties["testJavaVersion"]?.let(JavaVersion::toVersion) - val testOnOpenJ9 = gradle.startParameter.projectProperties["testJavaVM"]?.run { this == "openj9" } - ?: false - if (testOnOpenJ9 && testJavaVersion?.majorVersion == "18") { - enabled = false + // disable tests on openj9 18 because they often crash JIT compiler + val testJavaVersion = gradle.startParameter.projectProperties["testJavaVersion"]?.let(JavaVersion::toVersion) + val testOnOpenJ9 = gradle.startParameter.projectProperties["testJavaVM"]?.run { this == "openj9" } + ?: false + if (testOnOpenJ9 && testJavaVersion?.majorVersion == "18") { + enabled = false + } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index 68e1e5be6121..369a9fd22607 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.propagation.ContextPropagators; @@ -141,9 +140,6 @@ public OpenTelemetry openTelemetry( ContextPropagators propagators = propagatorsProvider.getIfAvailable(ContextPropagators::noop); - // global is needed for logging appenders - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); - return OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .setMeterProvider(meterProvider) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java new file mode 100644 index 000000000000..25763fbabf16 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.logging; + +import io.opentelemetry.api.OpenTelemetry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@SuppressWarnings("OtelPrivateConstructorForUtilityClass") +@ConditionalOnBean(OpenTelemetry.class) +public class OpenTelemetryAppenderAutoConfiguration { + + @Configuration + @ConditionalOnProperty( + prefix = "otel.springboot.log4j-appender", + name = "enabled", + matchIfMissing = true) + @ConditionalOnClass({ + io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender.class + }) + static class Log4jAppenderConfig { + + @Bean + ApplicationListener log4jOtelAppenderInitializer( + OpenTelemetry openTelemetry) { + return event -> { + io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender.install( + openTelemetry); + }; + } + } + + @Configuration + @ConditionalOnProperty( + prefix = "otel.springboot.logback-appender", + name = "enabled", + matchIfMissing = true) + @ConditionalOnClass({ + io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender.class + }) + static class LogbackAppenderConfig { + + @Bean + ApplicationListener logbackOtelAppenderInitializer( + OpenTelemetry openTelemetry) { + return event -> { + io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender.install( + openTelemetry); + }; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 2d5f5144909f..1f42548a23f6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -10,6 +10,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpa io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.kafka.KafkaInstrumentationAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.logging.OpenTelemetryAppenderAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.metrics.MicrometerShimAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration,\ diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 0e9748f20b63..86874a6f705b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -9,6 +9,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpa io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.kafka.KafkaInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.logging.OpenTelemetryAppenderAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.metrics.MicrometerShimAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index 35809251e5c8..67312508ced8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -8,7 +8,6 @@ import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration; @@ -17,7 +16,6 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -37,11 +35,6 @@ public OpenTelemetry customOpenTelemetry() { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName( "when Application Context contains OpenTelemetry bean should NOT initialize openTelemetry") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java index 0f61763ac496..3dc05203bf0a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ public class TraceAspectAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, TraceAspectAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when aspects are ENABLED should initialize WithSpanAspect bean") void aspectsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java index 7f48a09484cc..816e612236ed 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -28,11 +26,6 @@ class JaegerSpanExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, JaegerSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize JaegerGrpcSpanExporter bean") void exportersEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java index 62e30cdd899c..5b1e8355e1ec 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.logging.LoggingMetricExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -24,11 +22,6 @@ class LoggingMetricExporterAutoConfigurationTest { OpenTelemetryAutoConfiguration.class, LoggingMetricExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void loggingEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java index 3f0f9c2a20fa..34abb3200ec2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -26,11 +24,6 @@ class LoggingSpanExporterAutoConfigurationTest { OpenTelemetryAutoConfiguration.class, LoggingSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize LoggingSpanExporter bean") void loggingEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java index 8fde124356ab..540eac6ee217 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -23,11 +21,6 @@ class OtlpLogExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpLoggerExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void otlpEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java index 9b8ac94017c4..67b2365579f0 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -23,11 +21,6 @@ class OtlpMetricExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpMetricExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void otlpEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java index 108afea8856d..d606aae3adaa 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,11 +23,6 @@ class OtlpSpanExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize OtlpGrpcSpanExporter bean") void otlpEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java index f5120be939b1..7e6aa89a4515 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,11 +23,6 @@ class ZipkinSpanExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, ZipkinSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize ZipkinSpanExporter bean") void exportersEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java index 3a98e6a3f607..84224f625238 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ class RestTemplateAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, RestTemplateAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when httpclients are ENABLED should initialize RestTemplateInterceptor bean") void httpClientsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java index aaa7fc2468a5..ea90e219014c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ class WebClientAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, WebClientAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when httpclients are ENABLED should initialize WebClientBeanPostProcessor bean") void httpClientsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java index 4e62b622ee6b..7e66044cd0db 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java @@ -8,10 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.micrometer.core.instrument.MeterRegistry; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,11 +23,6 @@ class MicrometerShimAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, MicrometerShimAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void metricsEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java index b5bc165f0ced..a20162cec993 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ class PropagationAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when propagation is ENABLED should initialize PropagationAutoConfiguration bean") void shouldBeConfigured() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java index 7d0dbbe2306c..6c746b0c92be 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import java.util.Arrays; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ public class PropagationPropertiesTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when propagation is SET should set PropagationProperties with given propagators") void hasType() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java index e0ac7e58320f..17506fffe3ed 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -22,11 +20,6 @@ public class OtelResourceAutoConfigurationTest { AutoConfigurations.of( OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName( "when otel.springboot.resource.enabled is set to true configuration should be initialized") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java index 89881bafa64c..2b882b5cf7ee 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java @@ -8,8 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import io.opentelemetry.api.GlobalOpenTelemetry; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -21,11 +19,6 @@ public class OtelResourcePropertiesTest { .withPropertyValues("otel.springboot.resource.enabled=true") .withConfiguration(AutoConfigurations.of(OtelResourceAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when attributes are SET should set OtelResourceProperties with given attributes") void hasAttributes() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java index 969a81d5a369..93756ae1c4ed 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java @@ -8,8 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import io.opentelemetry.api.GlobalOpenTelemetry; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -19,11 +17,6 @@ class SpringResourceConfigPropertiesTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when map is set in properties in a row it should be available in config") void shouldInitializeAttributesByMapInArow() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java index 22ab4809381d..5383154c80c1 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import jakarta.servlet.Filter; import org.junit.jupiter.api.BeforeEach; @@ -29,7 +28,6 @@ class WebMvcFilterAutoConfigurationSpring6Test { @BeforeEach void setUp() { assumeTrue(Boolean.getBoolean("testLatestDeps")); - GlobalOpenTelemetry.resetForTest(); } @Test diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java index 4e44a5cbcf54..6c82ae182315 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeFalse; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import javax.servlet.Filter; import org.junit.jupiter.api.BeforeEach; @@ -28,7 +27,6 @@ class WebMvcFilterAutoConfigurationTest { @BeforeEach void setUp() { assumeFalse(Boolean.getBoolean("testLatestDeps")); - GlobalOpenTelemetry.resetForTest(); } @Test diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java new file mode 100644 index 000000000000..b27eef5142a2 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +class LogbackAppenderTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + @BeforeEach + void setUp() { + // reset the appender + OpenTelemetryAppender.install(null); + } + + @Configuration + static class TestingOpenTelemetryConfiguration { + + @Bean + public OpenTelemetry openTelemetry() { + return testing.getOpenTelemetry(); + } + } + + @Test + void shouldInitializeAppender() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + LoggerFactory.getLogger("test").info("test log message"); + + assertThat(testing.logRecords()) + .anySatisfy( + logRecord -> { + assertThat(logRecord.getInstrumentationScopeInfo().getName()).isEqualTo("test"); + assertThat(logRecord.getBody().asString()).contains("test log message"); + }); + } + + @Test + void shouldNotInitializeAppenderWhenDisabled() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + properties.put("otel.springboot.logback-appender.enabled", "false"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + LoggerFactory.getLogger("test").info("test log message"); + + assertThat(testing.logRecords()).isEmpty(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml new file mode 100644 index 000000000000..6b49823644ff --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java index e22444cb99b0..c70a9d73b4ab 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java @@ -13,12 +13,15 @@ import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.ReadWriteSpan; @@ -28,7 +31,6 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.time.Duration; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -41,6 +43,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { private static final OpenTelemetrySdk openTelemetry; private static final InMemorySpanExporter testSpanExporter; private static final InMemoryMetricExporter testMetricExporter; + private static final InMemoryLogRecordExporter testLogRecordExporter; private static final MetricReader metricReader; private static boolean forceFlushCalled; @@ -49,6 +52,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { testSpanExporter = InMemorySpanExporter.create(); testMetricExporter = InMemoryMetricExporter.create(AggregationTemporality.DELTA); + testLogRecordExporter = InMemoryLogRecordExporter.create(); metricReader = PeriodicMetricReader.builder(testMetricExporter) @@ -66,6 +70,10 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { .addSpanProcessor(SimpleSpanProcessor.create(testSpanExporter)) .build()) .setMeterProvider(SdkMeterProvider.builder().registerMetricReader(metricReader).build()) + .setLoggerProvider( + SdkLoggerProvider.builder() + .addLogRecordProcessor(SimpleLogRecordProcessor.create(testLogRecordExporter)) + .build()) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); } @@ -98,6 +106,7 @@ public void clearAllExportedData() { openTelemetry.getSdkMeterProvider().forceFlush().join(10, TimeUnit.SECONDS); testSpanExporter.reset(); testMetricExporter.reset(); + testLogRecordExporter.reset(); forceFlushCalled = false; } @@ -123,8 +132,7 @@ public List getExportedMetrics() { @Override public List getExportedLogRecords() { - // no logs support yet - return Collections.emptyList(); + return testLogRecordExporter.getFinishedLogRecordItems(); } @Override From fce77cc0446876fa7184028952a251fcab1e3c66 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 11 Jul 2023 18:48:52 +0300 Subject: [PATCH 84/95] Fix reactor-netty-1.0 latest dep tests (#8922) --- .../v1_0/TransportConnectorInstrumentation.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java b/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java index 690724a79440..4b165de020ee 100644 --- a/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java +++ b/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java @@ -7,6 +7,7 @@ import static io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0.ReactorNettySingletons.connectionInstrumenter; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -53,7 +54,13 @@ public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( named("doConnect") .and(takesArgument(0, List.class)) - .and(takesArgument(2, named("io.netty.channel.ChannelPromise"))) + .and( + takesArgument( + 2, + namedOneOf( + "io.netty.channel.ChannelPromise", + // since 1.0.34 + "reactor.netty.transport.TransportConnector$MonoChannelPromise"))) .and(takesArgument(3, int.class)), TransportConnectorInstrumentation.class.getName() + "$ConnectNewAdvice"); } From 7e4c0fd615783c9f4d10a1706362616bacaaef65 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 11 Jul 2023 09:07:55 -0700 Subject: [PATCH 85/95] Fix instrumentation of Azure SDK EventHubs library (#8916) --- .../v1_36/AzureSdkInstrumentationModule.java | 10 +++- .../com.azure.core.util.tracing.Tracer | 1 + .../src/test/java/AzureSdkTestOld.java | 60 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer create mode 100644 instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java index 2f3f4b44729a..212d10d10934 100644 --- a/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java @@ -7,7 +7,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static java.util.Arrays.asList; -import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.not; import com.google.auto.service.AutoService; @@ -30,6 +30,11 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) helperResourceBuilder.register( "META-INF/services/com.azure.core.util.tracing.TracerProvider", "azure-core-1.36/META-INF/services/com.azure.core.util.tracing.TracerProvider"); + // some azure sdks (e.g. EventHubs) are still looking up Tracer via service loader + // and not yet using the new TracerProvider + helperResourceBuilder.register( + "META-INF/services/com.azure.core.util.tracing.Tracer", + "azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer"); } @Override @@ -47,7 +52,8 @@ public List typeInstrumentations() { public static class EmptyTypeInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { - return named("com.azure.core.util.tracing.TracerProvider"); + return namedOneOf( + "com.azure.core.util.tracing.TracerProvider", "com.azure.core.util.tracing.Tracer"); } @Override diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer new file mode 100644 index 000000000000..00cafebf9a66 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer @@ -0,0 +1 @@ +io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracer diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java b/instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java new file mode 100644 index 000000000000..4adf7049c669 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import com.azure.core.util.Context; +import com.azure.core.util.tracing.Tracer; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Iterator; +import java.util.ServiceLoader; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +// some azure sdks (e.g. EventHubs) are still looking up Tracer via service loader +// and not yet using the new TracerProvider +class AzureSdkTestOld { + + @RegisterExtension + public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Test + void testHelperClassesInjected() { + com.azure.core.util.tracing.Tracer azTracer = createAzTracer(); + assertThat(azTracer.isEnabled()).isTrue(); + + assertThat(azTracer.getClass().getName()) + .isEqualTo( + "io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.shaded" + + ".com.azure.core.tracing.opentelemetry.OpenTelemetryTracer"); + } + + @Test + void testSpan() { + com.azure.core.util.tracing.Tracer azTracer = createAzTracer(); + Context context = azTracer.start("hello", Context.NONE); + azTracer.end(null, null, context); + + testing.waitAndAssertTracesWithoutScopeVersionVerification( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("hello") + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfying(Attributes::isEmpty))); + } + + private static com.azure.core.util.tracing.Tracer createAzTracer() { + Iterable tracers = + ServiceLoader.load(com.azure.core.util.tracing.Tracer.class); + Iterator it = tracers.iterator(); + return it.hasNext() ? it.next() : null; + } +} From 55368a52d7cbbb48628a1b8b97c66c8f14cc79a7 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:59:38 -0700 Subject: [PATCH 86/95] Rename messaging operation "send" to "publish" per spec (#8929) Co-authored-by: Lauri Tulmin --- .../instrumenter/messaging/MessageOperation.java | 2 +- .../messaging/MessagingAttributesExtractor.java | 2 +- .../MessagingAttributesExtractorTest.java | 2 +- .../messaging/MessagingSpanNameExtractorTest.java | 2 +- .../javaagent/src/jms2Test/groovy/Jms2Test.groovy | 2 +- .../javaagent/src/test/groovy/Jms1Test.groovy | 2 +- .../jms/v3_0/Jms3InstrumentationTest.java | 6 +++--- .../jms/JmsInstrumenterFactory.java | 2 +- .../kafkaclients/v0_11/KafkaClientDefaultTest.java | 6 +++--- .../v0_11/KafkaClientPropagationDisabledTest.java | 4 ++-- .../v0_11/KafkaClientSuppressReceiveSpansTest.java | 6 +++--- .../kafkaclients/v2_6/InterceptorsTest.java | 2 +- .../kafkaclients/v2_6/WrapperTest.java | 2 +- .../kafka/internal/KafkaInstrumenterFactory.java | 2 +- .../src/test/groovy/KafkaStreamsDefaultTest.groovy | 6 +++--- .../KafkaStreamsSuppressReceiveSpansTest.groovy | 4 ++-- .../pulsar/v2_8/telemetry/PulsarSingletons.java | 4 ++-- .../pulsar/v2_8/PulsarClientTest.groovy | 6 +++--- .../rabbitmq/RabbitInstrumenterHelper.java | 2 +- .../instrumentation/rabbitmq/RabbitSingletons.java | 2 +- .../javaagent/src/test/groovy/RabbitMqTest.groovy | 14 +++++++------- .../kafka/v1_0/AbstractReactorKafkaTest.java | 2 +- .../v4_8/RocketMqInstrumenterFactory.java | 2 +- .../v4_8/AbstractRocketMqClientTest.groovy | 8 ++++---- .../v5_0/RocketMqInstrumenterFactory.java | 2 +- ...tractRocketMqClientSuppressReceiveSpanTest.java | 2 +- .../v5_0/AbstractRocketMqClientTest.java | 6 +++--- .../autoconfigure/kafka/KafkaIntegrationTest.java | 2 +- .../groovy/SpringIntegrationAndRabbitTest.groovy | 2 +- .../v4_1/SpringIntegrationTelemetryBuilder.java | 4 ++-- .../AbstractSpringCloudStreamProducerTest.groovy | 2 +- .../src/test/groovy/SpringListenerTest.groovy | 2 +- .../src/test/groovy/SpringTemplateTest.groovy | 4 ++-- .../spring/jms/v6_0/SpringJmsListenerTest.java | 4 ++-- .../spring/kafka/v2_7/SpringKafkaTest.java | 10 +++++----- .../AbstractSpringKafkaNoReceiveTelemetryTest.java | 10 +++++----- .../spring/rabbit/v1_0/ContextPropagationTest.java | 2 +- .../kafka/v3_6/BatchRecordsVertxKafkaTest.java | 6 +++--- .../kafka/v3_6/SingleRecordVertxKafkaTest.java | 4 ++-- ...ReceiveTelemetryBatchRecordsVertxKafkaTest.java | 6 +++--- ...ReceiveTelemetrySingleRecordVertxKafkaTest.java | 4 ++-- 41 files changed, 82 insertions(+), 82 deletions(-) diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java index 34f266bb7803..2f6b14828ba1 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java @@ -13,7 +13,7 @@ * that may be used in a messaging system. */ public enum MessageOperation { - SEND, + PUBLISH, RECEIVE, PROCESS; diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java index 6cfe85d8465b..558f88a94d35 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java @@ -121,7 +121,7 @@ public void onEnd( @Override public SpanKey internalGetSpanKey() { switch (operation) { - case SEND: + case PUBLISH: return SpanKey.PRODUCER; case RECEIVE: return SpanKey.CONSUMER_RECEIVE; diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java index d98fde90fb06..7dbb7ddd60e8 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java @@ -92,7 +92,7 @@ static Stream destinations() { void shouldExtractNoAttributesIfNoneAreAvailable() { // given AttributesExtractor, String> underTest = - MessagingAttributesExtractor.create(TestGetter.INSTANCE, MessageOperation.SEND); + MessagingAttributesExtractor.create(TestGetter.INSTANCE, MessageOperation.PUBLISH); Context context = Context.root(); diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java index fb60066816b4..efca09dfc41b 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java @@ -49,7 +49,7 @@ void shouldExtractSpanName( static Stream spanNameParams() { return Stream.of( - Arguments.of(false, "destination", MessageOperation.SEND, "destination send"), + Arguments.of(false, "destination", MessageOperation.PUBLISH, "destination publish"), Arguments.of(true, null, MessageOperation.PROCESS, "(temporary) process"), Arguments.of(false, null, MessageOperation.RECEIVE, "unknown receive")); } diff --git a/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy b/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy index 770586e3cf87..c784c5dc0718 100644 --- a/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy +++ b/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy @@ -265,7 +265,7 @@ class Jms2Test extends AgentInstrumentationSpecification { static producerSpan(TraceAssert trace, int index, String destinationName, SpanData parentSpan = null) { trace.span(index) { - name destinationName + " send" + name destinationName + " publish" kind PRODUCER if (parentSpan == null) { hasNoParent() diff --git a/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy b/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy index 330dc33851df..77a9b92a7ec9 100644 --- a/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy +++ b/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy @@ -322,7 +322,7 @@ class Jms1Test extends AgentInstrumentationSpecification { static producerSpan(TraceAssert trace, int index, String destinationName, SpanData parentSpan = null, boolean testHeaders = false) { trace.span(index) { - name destinationName + " send" + name destinationName + " publish" kind PRODUCER if (parentSpan == null) { hasNoParent() diff --git a/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java b/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java index 463524cd2223..fcaad063fdfe 100644 --- a/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java +++ b/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java @@ -139,7 +139,7 @@ void testMessageConsumer(DestinationFactory destinationFactory, boolean isTempor trace.hasSpansSatisfyingExactly( span -> span.hasName("producer parent").hasNoParent(), span -> - span.hasName(producerDestinationName + " send") + span.hasName(producerDestinationName + " publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -206,7 +206,7 @@ void testMessageListener(DestinationFactory destinationFactory, boolean isTempor trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName(producerDestinationName + " send") + span.hasName(producerDestinationName + " publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -289,7 +289,7 @@ void shouldCaptureMessageHeaders(DestinationFactory destinationFactory, boolean trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName(producerDestinationName + " send") + span.hasName(producerDestinationName + " publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java b/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java index f8903ab378ff..86c467a13390 100644 --- a/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java +++ b/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java @@ -45,7 +45,7 @@ public JmsInstrumenterFactory setMessagingReceiveInstrumentationEnabled( public Instrumenter createProducerInstrumenter() { JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; return Instrumenter.builder( openTelemetry, diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index 093d6ea2e858..c7d6817975ee 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -85,7 +85,7 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, testHeaders)), @@ -134,7 +134,7 @@ void testPassThroughTombstone() trace -> { trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, null, false))); @@ -185,7 +185,7 @@ void testRecordsWithTopicPartitionKafkaConsume() trace -> { trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false))); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java index e465a4737b89..e3ff36a08f98 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java @@ -34,7 +34,7 @@ void testReadRemoteContextWhenPropagationIsDisabled() throws InterruptedExceptio trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, message, false)))); @@ -54,7 +54,7 @@ void testReadRemoteContextWhenPropagationIsDisabled() throws InterruptedExceptio trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, message, false))), diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java index f2b951b69553..1e8208310a34 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java @@ -62,7 +62,7 @@ void testKafkaProduceAndConsume() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, false)), @@ -100,7 +100,7 @@ void testPassThroughTombstone() trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, null, false)), @@ -138,7 +138,7 @@ void testRecordsWithTopicPartitionKafkaConsume() trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false)), diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java index 90f658c53113..175a3920f879 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java @@ -79,7 +79,7 @@ void testInterceptors() throws InterruptedException { span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(); }, span -> { - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java index 0777f55afbd1..46292d03c210 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java @@ -88,7 +88,7 @@ void testWrappers(boolean testHeaders) throws InterruptedException { span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(); }, span -> { - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(testHeaders)); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java index 216fb00c9645..4a9936899730 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java @@ -86,7 +86,7 @@ public Instrumenter createProducerInstrume Iterable> extractors) { KafkaProducerAttributesGetter getter = KafkaProducerAttributesGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; return Instrumenter.builder( openTelemetry, diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy index 385a078b8e52..7022383fff2b 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy @@ -84,7 +84,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { assertTraces(3) { traces.sort(orderByRootSpanName( - STREAM_PENDING + " send", + STREAM_PENDING + " publish", STREAM_PENDING + " receive", STREAM_PROCESSED + " receive")) @@ -93,7 +93,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { trace(0, 1) { // kafka-clients PRODUCER span(0) { - name STREAM_PENDING + " send" + name STREAM_PENDING + " publish" kind PRODUCER hasNoParent() attributes { @@ -150,7 +150,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { } // kafka-clients PRODUCER span(2) { - name STREAM_PROCESSED + " send" + name STREAM_PROCESSED + " publish" kind PRODUCER childOf span(1) attributes { diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy index d686ef0f8c57..479f46b899d9 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy @@ -88,7 +88,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { trace(0, 4) { // kafka-clients PRODUCER span(0) { - name STREAM_PENDING + " send" + name STREAM_PENDING + " publish" kind PRODUCER hasNoParent() attributes { @@ -127,7 +127,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { // kafka-clients PRODUCER span(2) { - name STREAM_PROCESSED + " send" + name STREAM_PROCESSED + " publish" kind PRODUCER childOf span(1) attributes { diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index 20f4975962e0..25a3499557c0 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -115,9 +115,9 @@ private static Instrumenter createProducerInstrumenter() { Instrumenter.builder( TELEMETRY, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, MessageOperation.SEND)) + MessagingSpanNameExtractor.create(getter, MessageOperation.PUBLISH)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.SEND)) + createMessagingAttributesExtractor(getter, MessageOperation.PUBLISH)) .addAttributesExtractor( NetClientAttributesExtractor.create(new PulsarNetClientAttributesGetter())); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy index d81238353cff..fa3c60f68d15 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy @@ -458,7 +458,7 @@ class PulsarClientTest extends AgentInstrumentationSpecification { kind INTERNAL hasNoParent() } - producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*send/, { it.startsWith(topic) }, msgId) + producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*publish/, { it.startsWith(topic) }, msgId) } } } @@ -503,7 +503,7 @@ class PulsarClientTest extends AgentInstrumentationSpecification { kind INTERNAL hasNoParent() } - producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*send/, { it.startsWith(topic) }, msgId) + producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*publish/, { it.startsWith(topic) }, msgId) receiveSpan(it, 2, span(1), topic, ~/${topic}-partition-.*receive/, { it.startsWith(topic) }, msgId) processSpan(it, 3, span(2), topic, ~/${topic}-partition-.*process/, { it.startsWith(topic) }, msgId) } @@ -578,7 +578,7 @@ class PulsarClientTest extends AgentInstrumentationSpecification { if (namePattern != null) { name namePattern } else { - name "$topic send" + name "$topic publish" } kind PRODUCER childOf parentSpan diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java index 512efdf54a21..b5f9e0ab351c 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java @@ -34,7 +34,7 @@ public static RabbitInstrumenterHelper helper() { public void onPublish(Span span, String exchange, String routingKey) { String exchangeName = normalizeExchangeName(exchange); span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_NAME, exchangeName); - span.updateName(exchangeName + " send"); + span.updateName(exchangeName + " publish"); if (routingKey != null && !routingKey.isEmpty()) { span.setAttribute(SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, routingKey); } diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java index 070ba7f6a61d..b770d8dbad51 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java @@ -56,7 +56,7 @@ private static Instrumenter createChannelInstrumenter() GlobalOpenTelemetry.get(), instrumentationName, ChannelAndMethod::getMethod) .addAttributesExtractor( buildMessagingAttributesExtractor( - RabbitChannelAttributesGetter.INSTANCE, MessageOperation.SEND)) + RabbitChannelAttributesGetter.INSTANCE, MessageOperation.PUBLISH)) .addAttributesExtractor( NetClientAttributesExtractor.create(new RabbitChannelNetAttributesGetter())) .addContextCustomizer( diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy b/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy index 9245f875a976..700d970505bd 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy +++ b/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy @@ -81,7 +81,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb rabbitSpan(it, 1, null, null, null, "exchange.declare", span(0)) rabbitSpan(it, 2, null, null, null, "queue.declare", span(0)) rabbitSpan(it, 3, null, null, null, "queue.bind", span(0)) - rabbitSpan(it, 4, exchangeName, routingKey, "send", "$exchangeName", span(0)) + rabbitSpan(it, 4, exchangeName, routingKey, "publish", "$exchangeName", span(0)) producerSpan = span(4) } @@ -124,7 +124,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb hasNoParent() } rabbitSpan(it, 1, null, null, null, "queue.declare", span(0)) - rabbitSpan(it, 2, "", null, "send", "", span(0)) + rabbitSpan(it, 2, "", null, "publish", "", span(0)) producerSpan = span(2) } @@ -185,7 +185,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb } (1..messageCount).each { trace(3 + it, 2) { - rabbitSpan(it, 0, exchangeName, null, "send", "$exchangeName") + rabbitSpan(it, 0, exchangeName, null, "publish", "$exchangeName") rabbitSpan(it, 1, exchangeName, null, "process", resource, span(0), null, null, null, setTimestamp) } } @@ -239,7 +239,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb rabbitSpan(it, 0, null, null, null, "basic.consume") } trace(4, 2) { - rabbitSpan(it, 0, exchangeName, null, "send", "$exchangeName") + rabbitSpan(it, 0, exchangeName, null, "publish", "$exchangeName") rabbitSpan(it, 1, exchangeName, null, "process", "", span(0), null, error, error.message) } } @@ -304,7 +304,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb hasNoParent() } rabbitSpan(it, 1, null, null, null, "queue.declare", span(0)) - rabbitSpan(it, 2, "", "some-routing-queue", "send", "", span(0)) + rabbitSpan(it, 2, "", "some-routing-queue", "publish", "", span(0)) producerSpan = span(2) } @@ -351,7 +351,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb rabbitSpan(it, 0, null, null, null, "queue.declare") } trace(1, 2) { - rabbitSpan(it, 0, "", null, "send", "", null, null, null, null, false, true) + rabbitSpan(it, 0, "", null, "publish", "", null, null, null, null, false, true) rabbitSpan(it, 1, "", null, "process", "", span(0), null, null, null, false, true) } trace(2, 1) { @@ -428,7 +428,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb "$SemanticAttributes.MESSAGING_DESTINATION_NAME" exchange "$SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY" { it == null || it == routingKey || it.startsWith("amq.gen-") } - if (operation != null && operation != "send") { + if (operation != null && operation != "publish") { "$SemanticAttributes.MESSAGING_OPERATION" operation } if (expectTimestamp) { diff --git a/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java b/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java index 068a1f5858a8..ab00e568e28a 100644 --- a/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java +++ b/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java @@ -151,7 +151,7 @@ protected void testSingleRecordProcess( trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testTopic send") + span.hasName("testTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java index ddacdc576e24..53d9ac6e12d7 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java @@ -35,7 +35,7 @@ static Instrumenter createProducerInstrumenter( boolean propagationEnabled) { RocketMqProducerAttributeGetter getter = RocketMqProducerAttributeGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; InstrumenterBuilder instrumenterBuilder = Instrumenter.builder( diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy index b34fd6551dc1..e187b041c51d 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy @@ -101,7 +101,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { assertTraces(1) { trace(0, 3) { span(0) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER attributes { "$SemanticAttributes.MESSAGING_SYSTEM" "rocketmq" @@ -154,7 +154,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { kind INTERNAL } span(1) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER childOf span(0) attributes { @@ -230,7 +230,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { kind INTERNAL } span(1) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER childOf span(0) attributes { @@ -314,7 +314,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { kind INTERNAL } span(1) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER childOf span(0) attributes { diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java index b2a0432ea8b6..41545e2a351f 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java @@ -32,7 +32,7 @@ private RocketMqInstrumenterFactory() {} public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry, List capturedHeaders) { RocketMqProducerAttributeGetter getter = RocketMqProducerAttributeGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; AttributesExtractor attributesExtractor = buildMessagingAttributesExtractor(getter, operation, capturedHeaders); diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java index 6fe8e4fdea4c..d253a86b3cdd 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java @@ -108,7 +108,7 @@ void testSendAndConsumeMessage() throws Throwable { span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java index 9d0dfcab91ff..e98132b34ca6 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java @@ -422,7 +422,7 @@ private static SpanDataAssert assertProducerSpan( attributeAssertions.addAll(Arrays.asList(extraAttributes)); return span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasAttributesSatisfyingExactly(attributeAssertions); } @@ -450,7 +450,7 @@ private static SpanDataAssert assertProducerSpanWithFifoMessage( attributeAssertions.addAll(Arrays.asList(extraAttributes)); return span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasAttributesSatisfyingExactly(attributeAssertions); } @@ -478,7 +478,7 @@ private static SpanDataAssert assertProducerSpanWithDelayMessage( attributeAssertions.addAll(Arrays.asList(extraAttributes)); return span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasAttributesSatisfyingExactly(attributeAssertions); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java index a086bed7b397..e224d0dfae9f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java @@ -103,7 +103,7 @@ private static void runShouldInstrumentProducerAndConsumer( trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testTopic send") + span.hasName("testTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy b/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy index 4df661a8b759..0163c5215992 100644 --- a/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy +++ b/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy @@ -51,7 +51,7 @@ class SpringIntegrationAndRabbitTest extends AgentInstrumentationSpecification i } span(3) { // span created by rabbitmq instrumentation - name "testTopic send" + name "testTopic publish" childOf span(1) kind PRODUCER attributes { diff --git a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java index 2d95cce3b6c0..c7b1eefe7458 100644 --- a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java +++ b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java @@ -71,7 +71,7 @@ private static String consumerSpanName(MessageWithChannel messageWithChannel) { } private static String producerSpanName(MessageWithChannel messageWithChannel) { - return messageWithChannel.getChannelName() + " send"; + return messageWithChannel.getChannelName() + " publish"; } /** @@ -101,7 +101,7 @@ public SpringIntegrationTelemetry build() { .addAttributesExtractor( buildMessagingAttributesExtractor( SpringMessagingAttributesGetter.INSTANCE, - MessageOperation.SEND, + MessageOperation.PUBLISH, capturedHeaders)) .buildInstrumenter(SpanKindExtractor.alwaysProducer()); return new SpringIntegrationTelemetry( diff --git a/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy b/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy index bee9853d2f0c..5a7f3f99995b 100644 --- a/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy +++ b/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy @@ -35,7 +35,7 @@ abstract class AbstractSpringCloudStreamProducerTest extends InstrumentationSpec name "producer" } span(1) { - name "testProducer.output send" + name "testProducer.output publish" childOf span(0) kind PRODUCER } diff --git a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy index e5db7900c1cc..c9d793b42e72 100644 --- a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy +++ b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy @@ -48,7 +48,7 @@ class SpringListenerTest extends AgentInstrumentationSpecification { static producerSpan(TraceAssert trace, int index, String destinationName, boolean testHeaders = false) { trace.span(index) { - name destinationName + " send" + name destinationName + " publish" kind PRODUCER hasNoParent() attributes { diff --git a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy index ddf49278d9f7..87e47083fb26 100644 --- a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy +++ b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy @@ -129,9 +129,9 @@ class SpringTemplateTest extends AgentInstrumentationSpecification { assertTraces(4) { traces.sort(orderByRootSpanName( "$destinationName receive", - "$destinationName send", + "$destinationName publish", "(temporary) receive", - "(temporary) send")) + "(temporary) publish")) trace(0, 1) { consumerSpan(it, 0, destinationName, msgId.get(), null, "receive") diff --git a/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java b/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java index cdb4d8c05128..cb83380f822d 100644 --- a/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java +++ b/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java @@ -107,7 +107,7 @@ void testSpringJmsListener(Class configClass) trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName("spring-jms-listener send") + span.hasName("spring-jms-listener publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -187,7 +187,7 @@ void shouldCaptureHeaders(Class configClass) trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName("spring-jms-listener send") + span.hasName("spring-jms-listener publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java b/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java index f07f5b172350..06b945a6e77a 100644 --- a/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java +++ b/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java @@ -62,7 +62,7 @@ void shouldCreateSpansForSingleRecordProcess() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -158,7 +158,7 @@ void shouldHandleFailureInKafkaListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -252,7 +252,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -269,7 +269,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { SemanticAttributes.MESSAGING_KAFKA_CLIENT_ID, stringAssert -> stringAssert.startsWith("producer"))), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -355,7 +355,7 @@ void shouldHandleFailureInKafkaBatchListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java b/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java index 07beb409d242..61166031fbbd 100644 --- a/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java +++ b/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java @@ -40,7 +40,7 @@ void shouldCreateSpansForSingleRecordProcess() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -110,7 +110,7 @@ void shouldHandleFailureInKafkaListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -180,7 +180,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -198,7 +198,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { AbstractLongAssert::isNotNegative), equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -270,7 +270,7 @@ void shouldHandleFailureInKafkaBatchListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java index d01343ea71de..aafe62daf551 100644 --- a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java +++ b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java @@ -154,7 +154,7 @@ public void test(boolean testHeaders) throws Exception { .hasSpansSatisfyingExactly( span -> span.hasName("parent"), span -> - span.hasName(" send") + span.hasName(" publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java index ebe96288d710..8e0eda18a78f 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java @@ -60,12 +60,12 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record1)), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record2))); @@ -131,7 +131,7 @@ void shouldHandleFailureInKafkaBatchListener() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java index 1cf2d317677b..8cfd1043f422 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java @@ -55,7 +55,7 @@ void shouldCreateSpansForSingleRecordProcess() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); @@ -96,7 +96,7 @@ void shouldHandleFailureInSingleRecordHandler() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java index f13fa40d8cb9..99ff87a9a9dc 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java @@ -62,7 +62,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { // first record span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record1)), @@ -75,7 +75,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { // second record span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record2)), @@ -123,7 +123,7 @@ void shouldHandleFailureInKafkaBatchListener() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record)), diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java index e824b55476ca..446e7356d037 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java @@ -48,7 +48,7 @@ void shouldCreateSpansForSingleRecordProcess() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record)), @@ -75,7 +75,7 @@ void shouldHandleFailureInSingleRecordHandler() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record)), From 57cfddcdd08d123f03746e0932d54ccee270f262 Mon Sep 17 00:00:00 2001 From: Raphael Philipe Mendes da Silva Date: Wed, 12 Jul 2023 10:58:46 -0700 Subject: [PATCH 87/95] Fix http attributes of AWS SDK V2 instrumentation (#8931) Signed-off-by: Raphael Silva Co-authored-by: Jean Bisutti --- .../aws-sdk-2.2/javaagent/build.gradle.kts | 2 +- .../src/test/java/QueryProtocolModelTest.java | 25 ++++ .../library-autoconfigure/build.gradle.kts | 1 - .../aws-sdk-2.2/library/build.gradle.kts | 3 +- .../v2_2/TracingExecutionInterceptor.java | 10 +- .../awssdk/v2_2/QueryProtocolModelTest.java | 28 +++++ .../aws-sdk-2.2/testing/build.gradle.kts | 1 + .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 91 +++++++++------ .../v2_2/AbstractAws2SqsTracingTest.groovy | 3 + .../v2_2/AbstractQueryProtocolModelTest.java | 110 ++++++++++++++++++ 10 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index e3e6e2d69713..0aa4f3625cd2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { testLibrary("software.amazon.awssdk:s3:2.2.0") testLibrary("software.amazon.awssdk:sqs:2.2.0") testLibrary("software.amazon.awssdk:sns:2.2.0") + testLibrary("software.amazon.awssdk:ses:2.2.0") } tasks { @@ -95,7 +96,6 @@ tasks { } withType().configureEach { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) // TODO run tests both with and without experimental span attributes systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", "true") } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java new file mode 100644 index 000000000000..d86c1a49d40d --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.awssdk.v2_2.AbstractQueryProtocolModelTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class QueryProtocolModelTest extends AbstractQueryProtocolModelTest { + @RegisterExtension + private final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Override + protected ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder(); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts index 3c4d6758fb47..4c6d8aacf0fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts @@ -23,7 +23,6 @@ dependencies { tasks { test { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 9f47f299644f..f50dd96dcfab 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { testLibrary("software.amazon.awssdk:kinesis:2.2.0") testLibrary("software.amazon.awssdk:rds:2.2.0") testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:ses:2.2.0") } testing { @@ -42,8 +43,6 @@ testing { tasks { withType { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) - // NB: If you'd like to change these, there is some cleanup work to be done, as most tests ignore this and // set the value directly (the "library" does not normally query it, only library-autoconfigure) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index b5850350f6ea..c344596f4ad9 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -137,9 +137,13 @@ public SdkRequest modifyRequest( } @Override - public void afterMarshalling( - Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { - + public void beforeTransmission( + Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + // In beforeTransmission we get access to the finalized http request, including modifications + // performed by other interceptors and the message signature. + // It is unlikely that further modifications are performed by the http client performing the + // request given that this would require the signature to be regenerated. + // // Since we merge the HTTP attributes into an already started span instead of creating a // full child span, we have to do some dirty work here. // diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java new file mode 100644 index 000000000000..d5ab73e541da --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class QueryProtocolModelTest extends AbstractQueryProtocolModelTest { + @RegisterExtension + public final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder() + .addExecutionInterceptor( + AwsSdkTelemetry.builder(testing.getOpenTelemetry()).build().newExecutionInterceptor()); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 4a7738056952..2a7f14d14b72 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { compileOnly("software.amazon.awssdk:s3:2.2.0") compileOnly("software.amazon.awssdk:sqs:2.2.0") compileOnly("software.amazon.awssdk:sns:2.2.0") + compileOnly("software.amazon.awssdk:ses:2.2.0") // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.12:1.0.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index a193d167cb0c..518ccedf3e73 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -52,13 +52,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { "Cannot check Sqs.SendMessage here due to hard-coded MD5.") } + // Force localhost instead of relying on mock server because using ip is yet another corner case of the virtual + // bucket changes introduced by aws sdk v2.18.0. When using IP, there is no way to prefix the hostname with the + // bucket name as label. + def clientUri = URI.create("http://localhost:${server.httpPort()}") + def "send #operation request with builder #builder.class.getName() mocked response"() { assumeSupportedConfig(service, operation) setup: configureSdkClient(builder) def client = builder - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .build() @@ -80,9 +85,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { kind operation != "SendMessage" ? CLIENT : PRODUCER hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + if (service == "S3") { + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://somebucket.localhost:${server.httpPort()}") || it.startsWith("http://localhost:${server.httpPort()}/somebucket") } + } else { + "$SemanticAttributes.NET_PEER_NAME" "localhost" + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://localhost:${server.httpPort()}") } + } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } "$SemanticAttributes.HTTP_METHOD" "$method" "$SemanticAttributes.HTTP_STATUS_CODE" 200 "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } @@ -111,17 +125,17 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { request.request().headers().get("traceparent") == null where: - service | operation | method | path | requestId | builder | call | body - "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" - "S3" | "GetObject" | "GET" | path("somebucket", "somekey") | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" - "Kinesis" | "DeleteStream" | "POST" | "" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" - "Sqs" | "CreateQueue" | "POST" | "" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + service | operation | method | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" + "Kinesis" | "DeleteStream" | "POST" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + "Sqs" | "SendMessage" | "POST" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ d41d8cd98f00b204e9800998ecf8427e @@ -131,14 +145,14 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { 27daac76-34dd-47df-bd01-1f6e873584a0 """ - "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ + "Ec2" | "AllocateAddress" | "POST" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "Rds" | "DeleteOptionGroup" | "POST" | "" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + "Rds" | "DeleteOptionGroup" | "POST" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 @@ -150,7 +164,7 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { setup: configureSdkClient(builder) def client = builder - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .build() @@ -171,9 +185,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { kind operation != "SendMessage" ? CLIENT : PRODUCER hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + if (service == "S3") { + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://somebucket.localhost:${server.httpPort()}") || it.startsWith("http://localhost:${server.httpPort()}") } + } else { + "$SemanticAttributes.NET_PEER_NAME" "localhost" + "$SemanticAttributes.HTTP_URL" "http://localhost:${server.httpPort()}" + } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } "$SemanticAttributes.HTTP_METHOD" "$method" "$SemanticAttributes.HTTP_STATUS_CODE" 200 "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } @@ -213,18 +236,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { } where: - service | operation | method | path | requestId | builder | call | body - "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" - "S3" | "GetObject" | "GET" | path("somebucket", "somekey") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" + service | operation | method | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" // Kinesis seems to expect an http2 response which is incompatible with our test server. // "Kinesis" | "DeleteStream" | "POST" | "/" | "UNKNOWN" | KinesisAsyncClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" - "Sqs" | "CreateQueue" | "POST" | "" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + "Sqs" | "SendMessage" | "POST" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ d41d8cd98f00b204e9800998ecf8427e @@ -234,19 +257,19 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { 27daac76-34dd-47df-bd01-1f6e873584a0 """ - "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ + "Ec2" | "AllocateAddress" | "POST" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "Rds" | "DeleteOptionGroup" | "POST" | "" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + "Rds" | "DeleteOptionGroup" | "POST" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 """ - "Sns" | "Publish" | "POST" | "" | "f187a3c1-376f-11df-8963-01868b7c937a" | SnsAsyncClient.builder() | { SnsAsyncClient c -> c.publish(r -> r.message("hello")) } | """ + "Sns" | "Publish" | "POST" | "f187a3c1-376f-11df-8963-01868b7c937a" | SnsAsyncClient.builder() | { SnsAsyncClient c -> c.publish(r -> r.message("hello")) } | """ 94f20ce6-13c5-43a0-9a9e-ca52d816e90b @@ -270,7 +293,7 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { .overrideConfiguration(createOverrideConfigurationBuilder() .retryPolicy(RetryPolicy.builder().numRetries(1).build()) .build()) - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .httpClientBuilder(ApacheHttpClient.builder().socketTimeout(Duration.ofMillis(50))) @@ -282,7 +305,6 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { then: thrown SdkClientException - def path = path("somebucket", "somekey") assertTraces(1) { trace(0, 1) { span(0) { @@ -292,13 +314,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { errorEvent SdkClientException, "Unable to execute HTTP request: Read timed out" hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it == "http://somebucket.localhost:${server.httpPort()}/somekey" || it == "http://localhost:${server.httpPort()}/somebucket/somekey" } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "${server.httpUri()}${path}" "$SemanticAttributes.HTTP_METHOD" "GET" "$SemanticAttributes.RPC_SYSTEM" "aws-api" "$SemanticAttributes.RPC_SERVICE" "S3" "$SemanticAttributes.RPC_METHOD" "GetObject" + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "aws.agent" "java-aws-sdk" "aws.bucket.name" "somebucket" } @@ -306,16 +333,4 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { } } } - - static String path(String bucket, String path = null) { - def result = "" - // since 2.18.0 bucket name is not present in request path - if (!Boolean.getBoolean("testLatestDeps") && !bucket.isEmpty()) { - result = "/" + bucket - } - if (path != null && !path.isEmpty()) { - result += "/" + path - } - return result - } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index a3529957b636..27b8e7e3f222 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -171,6 +171,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.method" "POST" "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.name" "localhost" "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } @@ -310,6 +311,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.method" "POST" "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.name" "localhost" "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } @@ -364,6 +366,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } "net.peer.name" "localhost" + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java new file mode 100644 index 000000000000..37274a7a961e --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.testing.internal.armeria.common.HttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpStatus; +import io.opentelemetry.testing.internal.armeria.common.MediaType; +import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ses.SesClient; +import software.amazon.awssdk.services.ses.model.Body; +import software.amazon.awssdk.services.ses.model.Content; +import software.amazon.awssdk.services.ses.model.Destination; +import software.amazon.awssdk.services.ses.model.Message; +import software.amazon.awssdk.services.ses.model.SendEmailRequest; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractQueryProtocolModelTest { + private final MockWebServerExtension server = new MockWebServerExtension(); + + @BeforeAll + public void setup() { + server.start(); + } + + @AfterAll + public void end() { + server.stop(); + } + + @BeforeEach + public void setupEach() { + server.beforeTestExecution(null); + } + + protected abstract ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder(); + + protected abstract InstrumentationExtension getTesting(); + + @Test + void testClientWithQueryProtocolModel() { + server.enqueue( + HttpResponse.of( + HttpStatus.OK, + MediaType.PLAIN_TEXT_UTF_8, + "12345")); + SesClient ses = + SesClient.builder() + .endpointOverride(server.httpUri()) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("foo", "bar"))) + .overrideConfiguration(createClientOverrideConfigurationBuilder().build()) + .region(Region.US_WEST_2) + .build(); + + Destination destination = Destination.builder().toAddresses("dest@test.com").build(); + Content content = Content.builder().data("content").build(); + Content sub = Content.builder().data("subject").build(); + Body body = Body.builder().html(content).build(); + Message msg = Message.builder().subject(sub).body(body).build(); + SendEmailRequest emailRequest = + SendEmailRequest.builder() + .destination(destination) + .message(msg) + .source("source@test.com") + .build(); + + ses.sendEmail(emailRequest); + + getTesting() + .waitAndAssertTraces( + trace -> { + trace.hasSpansSatisfyingExactly( + span -> { + span.hasKind(SpanKind.CLIENT); + span.hasAttributesSatisfying( + attributes -> { + assertThat(attributes) + .hasEntrySatisfying( + HTTP_URL, + entry -> { + assertThat(entry) + .satisfies( + value -> { + URI uri = URI.create(value); + assertThat(uri.getQuery()).isNull(); + }); + }); + }); + }); + }); + } +} From 70247f095b06ee376ed6309cb7a560a0992f5289 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 12 Jul 2023 21:00:49 +0300 Subject: [PATCH 88/95] Extract query arguments without regex on lettuce 6 (#8932) --- .../v5_1/LettuceInstrumentationModule.java | 5 +++ .../core/protocol/OtelCommandArgsUtil.java | 40 +++++++++++++++++++ .../lettuce/v5_1/OpenTelemetryTracing.java | 11 +++-- 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java index a9a003fc98de..fb909ced0afa 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java @@ -26,6 +26,11 @@ public ElementMatcher.Junction classLoaderMatcher() { return hasClassesNamed("io.lettuce.core.tracing.Tracing"); } + @Override + public boolean isHelperClass(String className) { + return className.startsWith("io.lettuce.core.protocol.OtelCommandArgsUtil"); + } + @Override public List typeInstrumentations() { return asList( diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java new file mode 100644 index 000000000000..ee0b3e3d0886 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.lettuce.core.protocol; + +import io.lettuce.core.protocol.CommandArgs.KeyArgument; +import io.lettuce.core.protocol.CommandArgs.SingularArgument; +import io.lettuce.core.protocol.CommandArgs.ValueArgument; +import io.opentelemetry.instrumentation.lettuce.common.LettuceArgSplitter; +import java.util.ArrayList; +import java.util.List; + +// Helper class for accessing package private fields in CommandArgs and its inner classes. +// https://github.com/lettuce-io/lettuce-core/blob/main/src/main/java/io/lettuce/core/protocol/CommandArgs.java +public final class OtelCommandArgsUtil { + + /** + * Extract argument {@link List} from {@link CommandArgs} so that we wouldn't need to parse them + * from command {@link String} with {@link LettuceArgSplitter#splitArgs}. + */ + public static List getCommandArgs(CommandArgs commandArgs) { + List result = new ArrayList<>(); + for (SingularArgument argument : commandArgs.singularArguments) { + String value = argument.toString(); + if (argument instanceof KeyArgument && value.startsWith("key<") && value.endsWith(">")) { + value = value.substring("key<".length(), value.length() - 1); + } else if (argument instanceof ValueArgument + && value.startsWith("value<") + && value.endsWith(">")) { + value = value.substring("value<".length(), value.length() - 1); + } + result.add(value); + } + return result; + } + + private OtelCommandArgsUtil() {} +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java index f71bf0d29402..b504bd51727e 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java @@ -10,6 +10,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.lettuce.core.output.CommandOutput; import io.lettuce.core.protocol.CompleteableCommand; +import io.lettuce.core.protocol.OtelCommandArgsUtil; import io.lettuce.core.protocol.RedisCommand; import io.lettuce.core.tracing.TraceContext; import io.lettuce.core.tracing.TraceContextProvider; @@ -170,7 +171,8 @@ private static class OpenTelemetrySpan extends Tracer.Span { @Nullable private List events; @Nullable private Throwable error; @Nullable private Span span; - @Nullable private String args; + @Nullable private List argsList; + @Nullable private String argsString; OpenTelemetrySpan(Context context, SpanBuilder spanBuilder, RedisCommandSanitizer sanitizer) { this.context = context; @@ -224,7 +226,7 @@ public synchronized Tracer.Span start(RedisCommand command) { span.updateName(command.getType().name()); if (command.getArgs() != null) { - args = command.getArgs().toCommandString(); + argsList = OtelCommandArgsUtil.getCommandArgs(command.getArgs()); } if (command instanceof CompleteableCommand) { @@ -294,7 +296,7 @@ public synchronized Tracer.Span annotate(String value) { @CanIgnoreReturnValue public synchronized Tracer.Span tag(String key, String value) { if (key.equals("redis.args")) { - args = value; + argsString = value; return this; } if (span != null) { @@ -325,7 +327,8 @@ public synchronized void finish() { private void finish(Span span) { if (name != null) { - String statement = sanitizer.sanitize(name, splitArgs(args)); + String statement = + sanitizer.sanitize(name, argsList != null ? argsList : splitArgs(argsString)); span.setAttribute(SemanticAttributes.DB_STATEMENT, statement); } span.end(); From 96902a68a89a9e4ab33e2db7c2928d67beff28ed Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 12 Jul 2023 11:45:53 -0700 Subject: [PATCH 89/95] Update change log (#8930) --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb545e90df7d..42858410922c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,71 @@ ## Unreleased +### Migration notes + +- Rename HTTP configuration settings + ([#8758](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8758)) + - `otel.instrumentation.http.capture-headers.client.request` + → `otel.instrumentation.http.client.capture-request-headers` + - `otel.instrumentation.http.capture-headers.client.response` + → `otel.instrumentation.http.client.capture-response-headers` + - `otel.instrumentation.http.capture-headers.server.request` + → `otel.instrumentation.http.server.capture-request-headers` + - `otel.instrumentation.http.capture-headers.server.response` + → `otel.instrumentation.http.server.capture-response-headers` + +### 📈 Enhancements + +- Support latest armeria release + ([#8745](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8745)) +- Support latest mongo release + ([#8785](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8785)) +- Remove `server.{address,port}` from HTTP server metrics + ([#8771](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8771)) +- aws-sdk-2.2.: Support injection into SQS.SendMessageBatch message attributes + ([#8798](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8798)) +- Log4j and Logback appenders opt-in to using GlobalOpenTelemetry + ([#8791](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8791)) +- aws-sdk-2.2: SNS.Publish support with experimental messaging propagator flag + ([#8830](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8830)) +- support for adding baggage to log4j 2 ContextData + ([#8810](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8810)) +- Micrometer bridge: interpret no SLO config as no buckets advice + ([#8856](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8856)) +- Instrumentation for Elasticsearch 8+ + ([#8799](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8799)) +- Add support for schemaUrls auto-computed from `AttributesExtrator`s + ([#8864](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8864)) +- Initialize appenders in the spring boot starter + ([#8888](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8888)) +- Support reactor-netty 1.0.34+ + ([#8922](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8922)) +- Rename messaging operation "send" to "publish" per spec + ([#8929](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8929)) +- Extract query arguments without regex on lettuce 6 + ([#8932](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8932)) + +### 🛠️ Bug fixes + +- Fix logging timestamp + ([#8761](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8761)) +- Minor fixes to the `server.*` attributes extrator + ([#8772](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8772)) +- Fix context leak on call to AmazonS3.generatePresignedUrl + ([#8815](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8815)) +- Fix exception when pulsar has multiple service addresses + ([#8816](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8816)) +- Fix NPE in aws instrumentation on duplicate TracingExecutionInterceptor + ([#8896](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8896)) +- (micrometer) don't add . to empty unit with prometheus naming conventions + ([#8872](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8872)) +- Set server span name for aborted requests in quarkus resteasy native + ([#8891](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8891)) +- Fix instrumentation of Azure SDK EventHubs library + ([#8916](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8916)) +- Fix http attributes of AWS SDK V2 instrumentation + ([#8931](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8931)) + ## Version 1.27.0 (2023-06-14) ### Migration notes From 5c9b40bc026be32aad095cef3dea0e00df6d3e69 Mon Sep 17 00:00:00 2001 From: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Date: Wed, 12 Jul 2023 12:21:06 -0700 Subject: [PATCH 90/95] Update version to 1.29.0-SNAPSHOT (#8937) --- CHANGELOG.md | 2 ++ examples/distro/build.gradle | 6 +++--- examples/extension/build.gradle | 8 ++++---- version.gradle.kts | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42858410922c..5d424a0e347e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## Version 1.28.0 (2023-07-12) + ### Migration notes - Rename HTTP configuration settings diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index bfafc945e327..562746ac3667 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -14,7 +14,7 @@ buildscript { dependencies { classpath "com.diffplug.spotless:spotless-plugin-gradle:6.19.0" classpath "gradle.plugin.com.github.johnrengelman:shadow:8.0.0" - classpath "io.opentelemetry.instrumentation:gradle-plugins:1.28.0-alpha-SNAPSHOT" + classpath "io.opentelemetry.instrumentation:gradle-plugins:1.29.0-alpha-SNAPSHOT" } } @@ -30,8 +30,8 @@ subprojects { opentelemetrySdk : "1.28.0", // these lines are managed by .github/scripts/update-version.sh - opentelemetryJavaagent : "1.28.0-SNAPSHOT", - opentelemetryJavaagentAlpha: "1.28.0-alpha-SNAPSHOT", + opentelemetryJavaagent : "1.29.0-SNAPSHOT", + opentelemetryJavaagentAlpha: "1.29.0-alpha-SNAPSHOT", bytebuddy : "1.14.5", autoservice : "1.1.1", diff --git a/examples/extension/build.gradle b/examples/extension/build.gradle index eb0bd34f3363..249206bc5712 100644 --- a/examples/extension/build.gradle +++ b/examples/extension/build.gradle @@ -13,8 +13,8 @@ plugins { id "com.github.johnrengelman.shadow" version "8.1.1" id "com.diffplug.spotless" version "6.19.0" - id "io.opentelemetry.instrumentation.muzzle-generation" version "1.28.0-alpha-SNAPSHOT" - id "io.opentelemetry.instrumentation.muzzle-check" version "1.28.0-alpha-SNAPSHOT" + id "io.opentelemetry.instrumentation.muzzle-generation" version "1.29.0-alpha-SNAPSHOT" + id "io.opentelemetry.instrumentation.muzzle-check" version "1.29.0-alpha-SNAPSHOT" } group 'io.opentelemetry.example' @@ -26,8 +26,8 @@ ext { opentelemetrySdk : "1.28.0", // these lines are managed by .github/scripts/update-version.sh - opentelemetryJavaagent : "1.28.0-SNAPSHOT", - opentelemetryJavaagentAlpha: "1.28.0-alpha-SNAPSHOT", + opentelemetryJavaagent : "1.29.0-SNAPSHOT", + opentelemetryJavaagentAlpha: "1.29.0-alpha-SNAPSHOT", junit : "5.9.3" ] diff --git a/version.gradle.kts b/version.gradle.kts index c1c97dc25c8d..7e23763e464e 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.28.0-SNAPSHOT" -val alphaVersion = "1.28.0-alpha-SNAPSHOT" +val stableVersion = "1.29.0-SNAPSHOT" +val alphaVersion = "1.29.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From a3935c6faeff4e89e3c9f2a607c3e7408bb39621 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 13 Jul 2023 14:45:02 +0300 Subject: [PATCH 91/95] Remove okhttp debug output from smoke tests (#8944) --- .../smoketest/AbstractTestContainerManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java index 2a68f49b2e43..0e38f96dbaab 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java @@ -23,7 +23,11 @@ protected Map getAgentEnvironment( // while modern JVMs understand linux container memory limits, they do not understand windows // container memory limits yet, so we need to explicitly set max heap in order to prevent the // JVM from taking too much memory and hitting the windows container memory limit - environment.put(jvmArgsEnvVarName, "-Xmx512m -javaagent:/" + TARGET_AGENT_FILENAME); + environment.put( + jvmArgsEnvVarName, + "-Xmx512m -javaagent:/" + + TARGET_AGENT_FILENAME + + " -Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.okhttp3.internal.concurrent.TaskRunner=INFO"); environment.put("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "1"); environment.put("OTEL_BSP_SCHEDULE_DELAY", "10ms"); environment.put("OTEL_METRIC_EXPORT_INTERVAL", "1000"); From 00d698ef87b14e45a35ad9f0245e4033c71a118a Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 13 Jul 2023 14:47:33 +0300 Subject: [PATCH 92/95] Fix flaky paly-ws test (#8943) --- .../main/groovy/PlayWsClientTestBase.groovy | 52 +++++++++++++++++-- .../groovy/PlayWsClientTestBaseBase.groovy | 34 ++++++++---- 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy b/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy index 351e70bdedef..52f47021b4d3 100644 --- a/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy +++ b/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy @@ -23,10 +23,12 @@ import java.util.concurrent.TimeUnit class PlayJavaWsClientTestBase extends PlayWsClientTestBaseBase { @Shared StandaloneWSClient wsClient + @Shared + StandaloneWSClient wsClientWithReadTimeout @Override StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - def request = wsClient.url(uri.toURL().toString()).setFollowRedirects(true) + def request = getClient(uri).url(uri.toURL().toString()).setFollowRedirects(true) headers.entrySet().each { entry -> request.addHeader(entry.getKey(), entry.getValue()) } return request.setMethod(method) } @@ -43,22 +45,33 @@ class PlayJavaWsClientTestBase extends PlayWsClientTestBaseBase { @Shared StandaloneWSClient wsClient + @Shared + StandaloneWSClient wsClientWithReadTimeout @Override StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - def request = wsClient.url(uri.toURL().toString()).setFollowRedirects(true) + def request = getClient(uri).url(uri.toURL().toString()).setFollowRedirects(true) headers.entrySet().each { entry -> request.addHeader(entry.getKey(), entry.getValue()) } request.setMethod(method) return request @@ -88,22 +101,33 @@ class PlayJavaStreamedWsClientTestBase extends PlayWsClientTestBaseBase { @Shared play.api.libs.ws.StandaloneWSClient wsClient + @Shared + play.api.libs.ws.StandaloneWSClient wsClientWithReadTimeout @Override play.api.libs.ws.StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - return wsClient.url(uri.toURL().toString()) + return getClient(uri).url(uri.toURL().toString()) .withMethod(method) .withFollowRedirects(true) .withHttpHeaders(JavaConverters.mapAsScalaMap(headers).toSeq()) @@ -135,22 +159,33 @@ class PlayScalaWsClientTestBase extends PlayWsClientTestBaseBase { @Shared play.api.libs.ws.StandaloneWSClient wsClient + @Shared + play.api.libs.ws.StandaloneWSClient wsClientWithReadTimeout @Override play.api.libs.ws.StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - return wsClient.url(uri.toURL().toString()) + return getClient(uri).url(uri.toURL().toString()) .withMethod(method) .withFollowRedirects(true) .withHttpHeaders(JavaConverters.mapAsScalaMap(headers).toSeq()) @@ -193,11 +228,20 @@ class PlayScalaStreamedWsClientTestBase extends PlayWsClientTestBaseBase extends HttpClientTest @Shared AsyncHttpClient asyncHttpClient + @Shared + AsyncHttpClient asyncHttpClientWithReadTimeout + @Shared ActorMaterializer materializer @@ -44,21 +47,30 @@ abstract class PlayWsClientTestBaseBase extends HttpClientTest // failure ahc will try the next address which isn't necessary for this test. RequestBuilderBase.DEFAULT_NAME_RESOLVER = new CustomNameResolver(ImmediateEventExecutor.INSTANCE) - AsyncHttpClientConfig asyncHttpClientConfig = - new DefaultAsyncHttpClientConfig.Builder() - .setMaxRequestRetry(0) - .setShutdownQuietPeriod(0) - .setShutdownTimeout(0) - .setMaxRedirects(3) - .setConnectTimeout(CONNECT_TIMEOUT_MS) - .setReadTimeout(READ_TIMEOUT_MS) - .build() - - asyncHttpClient = new DefaultAsyncHttpClient(asyncHttpClientConfig) + asyncHttpClient = createClient(false) + asyncHttpClientWithReadTimeout = createClient(true) + } + + def createClient(boolean readTimeout) { + DefaultAsyncHttpClientConfig.Builder builder = new DefaultAsyncHttpClientConfig.Builder() + .setMaxRequestRetry(0) + .setShutdownQuietPeriod(0) + .setShutdownTimeout(0) + .setMaxRedirects(3) + .setConnectTimeout(CONNECT_TIMEOUT_MS) + + if (readTimeout) { + builder.setReadTimeout(READ_TIMEOUT_MS) + } + + AsyncHttpClientConfig asyncHttpClientConfig =builder.build() + return new DefaultAsyncHttpClient(asyncHttpClientConfig) } def cleanupSpec() { system?.terminate() + asyncHttpClient?.close() + asyncHttpClientWithReadTimeout?.close() } @Override From 6f745e8f5323b493936c50393685d316c0795ebd Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Thu, 13 Jul 2023 14:56:56 +0200 Subject: [PATCH 93/95] Include OTel appenders in the Spring Starter (#8945) --- .../spring/spring-boot-autoconfigure/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index ce279dbc5159..27283a684df6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -25,6 +25,10 @@ dependencies { compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") implementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.3:library")) implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation(project(":instrumentation:log4j:log4j-appender-2.17:library")) + compileOnly("org.apache.logging.log4j:log4j-core:2.17.0") + implementation(project(":instrumentation:logback:logback-appender-1.0:library")) + compileOnly("ch.qos.logback:logback-classic:1.0.0") library("org.springframework.kafka:spring-kafka:2.9.0") library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") @@ -41,10 +45,6 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") compileOnly("io.opentelemetry:opentelemetry-exporter-zipkin") compileOnly(project(":instrumentation-annotations")) - compileOnly(project(":instrumentation:log4j:log4j-appender-2.17:library")) - compileOnly("org.apache.logging.log4j:log4j-core:2.17.0") - compileOnly(project(":instrumentation:logback:logback-appender-1.0:library")) - compileOnly("ch.qos.logback:logback-classic:1.0.0") compileOnly(project(":instrumentation:resources:library")) annotationProcessor("com.google.auto.service:auto-service") From 1e9b47b4c35f9046cec3718cadbc7410fdd9ffe1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 14 Jul 2023 01:39:51 -0700 Subject: [PATCH 94/95] Update RPC metrics view under stable http semconv (#8948) --- .../api/instrumenter/rpc/MetricsView.java | 8 + .../api/instrumenter/rpc/MetricsViewTest.java | 144 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java index 16d427c536b5..66da434d99f6 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashSet; import java.util.Set; @@ -31,6 +32,13 @@ private static Set buildAlwaysInclude() { view.add(SemanticAttributes.RPC_SERVICE); view.add(SemanticAttributes.RPC_METHOD); view.add(SemanticAttributes.RPC_GRPC_STATUS_CODE); + // stable http semconv + view.add(NetworkAttributes.NETWORK_TYPE); + view.add(NetworkAttributes.NETWORK_TRANSPORT); + view.add(NetworkAttributes.SERVER_ADDRESS); + view.add(NetworkAttributes.SERVER_PORT); + view.add(NetworkAttributes.SERVER_SOCKET_ADDRESS); + view.add(NetworkAttributes.SERVER_SOCKET_PORT); return view; } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java new file mode 100644 index 000000000000..0197b66e1e3d --- /dev/null +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java @@ -0,0 +1,144 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.rpc; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyClientView; +import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyServerView; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.junit.jupiter.api.Test; + +class MetricsViewTest { + + @Test + void shouldApplyClientView() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(SemanticAttributes.NET_PEER_NAME, "example.com") + .put(SemanticAttributes.NET_PEER_PORT, 8080) + .put(SemanticAttributes.NET_TRANSPORT, "ip_tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyClientView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(SemanticAttributes.NET_PEER_NAME, "example.com"), + entry(SemanticAttributes.NET_PEER_PORT, 8080L), + entry(SemanticAttributes.NET_TRANSPORT, "ip_tcp")); + } + + @Test + void shouldApplyClientView_stableHttpSemconv() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(NetworkAttributes.SERVER_ADDRESS, "example.com") + .put(NetworkAttributes.SERVER_PORT, 8080) + .put(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1") + .put(NetworkAttributes.SERVER_SOCKET_PORT, 12345) + .put(NetworkAttributes.NETWORK_TYPE, "ipv4") + .put(NetworkAttributes.NETWORK_TRANSPORT, "tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyClientView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(NetworkAttributes.SERVER_ADDRESS, "example.com"), + entry(NetworkAttributes.SERVER_PORT, 8080L), + entry(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1"), + entry(NetworkAttributes.SERVER_SOCKET_PORT, 12345L), + entry(NetworkAttributes.NETWORK_TYPE, "ipv4"), + entry(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + } + + @Test + void shouldApplyServerView() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(SemanticAttributes.NET_HOST_NAME, "example.com") + .put(SemanticAttributes.NET_SOCK_HOST_ADDR, "127.0.0.1") + .put(SemanticAttributes.NET_HOST_PORT, 8080) + .put(SemanticAttributes.NET_TRANSPORT, "ip_tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyServerView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(SemanticAttributes.NET_HOST_NAME, "example.com"), + entry(SemanticAttributes.NET_TRANSPORT, "ip_tcp")); + } + + @Test + void shouldApplyServerView_stableHttpSemconv() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(NetworkAttributes.SERVER_ADDRESS, "example.com") + .put(NetworkAttributes.SERVER_PORT, 8080) + .put(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1") + .put(NetworkAttributes.SERVER_SOCKET_PORT, 12345) + .put(NetworkAttributes.NETWORK_TYPE, "ipv4") + .put(NetworkAttributes.NETWORK_TRANSPORT, "tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyServerView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(NetworkAttributes.SERVER_ADDRESS, "example.com"), + entry(NetworkAttributes.SERVER_PORT, 8080L), + entry(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1"), + entry(NetworkAttributes.SERVER_SOCKET_PORT, 12345L), + entry(NetworkAttributes.NETWORK_TYPE, "ipv4"), + entry(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + } +} From 655ad346c792acb46534a1ae2cf30540e7269236 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Mon, 19 Jun 2023 11:06:46 +0200 Subject: [PATCH 95/95] Make server.socket.* attributes on the HTTP server side opt-in --- .../http/HttpClientAttributesExtractor.java | 3 +- .../http/HttpServerAttributesExtractor.java | 10 ++- .../HttpServerAttributesExtractorBuilder.java | 21 ++++- .../net/NetClientAttributesExtractor.java | 3 +- .../net/NetServerAttributesExtractor.java | 3 +- .../network/ServerAttributesExtractor.java | 3 +- .../InternalServerAttributesExtractor.java | 11 ++- .../HttpServerAttributesExtractorTest.java | 1 + ...verAttributesExtractorBothSemconvTest.java | 1 + ...rAttributesExtractorStableSemconvTest.java | 90 +++++++++++-------- 10 files changed, 99 insertions(+), 47 deletions(-) diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java index 926d55e3a409..23744c5bd2d7 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java @@ -98,7 +98,8 @@ public static HttpClientAttributesExtractorBuilder HttpServerAttributesExtractorBuilder httpAttributesGetter, NetServerAttributesGetter netAttributesGetter, List capturedRequestHeaders, - List capturedResponseHeaders) { + List capturedResponseHeaders, + boolean captureServerSocketAttributes) { this( httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders, + captureServerSocketAttributes, HttpRouteHolder::getRoute); } @@ -93,6 +95,7 @@ public static HttpServerAttributesExtractorBuilder netAttributesGetter, List capturedRequestHeaders, List capturedResponseHeaders, + boolean captureServerSocketAttributes, Function httpRouteHolderGetter) { super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); HttpNetAddressPortExtractor addressPortExtractor = @@ -119,7 +122,10 @@ public static HttpServerAttributesExtractorBuilder( netAttributesGetter, diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java index eccb8ba08175..06ad4ad9922f 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java @@ -19,6 +19,7 @@ public final class HttpServerAttributesExtractorBuilder { final NetServerAttributesGetter netAttributesGetter; List capturedRequestHeaders = emptyList(); List capturedResponseHeaders = emptyList(); + boolean captureServerSocketAttributes = false; HttpServerAttributesExtractorBuilder( HttpServerAttributesGetter httpAttributesGetter, @@ -64,12 +65,30 @@ public HttpServerAttributesExtractorBuilder setCapturedRespon return this; } + /** + * Configures the extractor to capture the optional {@code server.socket.address} and {@code + * server.socket.port} attributes, which are not collected by default. + * + * @param captureServerSocketAttributes {@code true} if the extractor should collect the optional + * {@code server.socket.address} and {@code server.socket.port} attributes. + */ + @CanIgnoreReturnValue + public HttpServerAttributesExtractorBuilder setCaptureServerSocketAttributes( + boolean captureServerSocketAttributes) { + this.captureServerSocketAttributes = captureServerSocketAttributes; + return this; + } + /** * Returns a new {@link HttpServerAttributesExtractor} with the settings of this {@link * HttpServerAttributesExtractorBuilder}. */ public AttributesExtractor build() { return new HttpServerAttributesExtractor<>( - httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); + httpAttributesGetter, + netAttributesGetter, + capturedRequestHeaders, + capturedResponseHeaders, + captureServerSocketAttributes); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java index d242794ebff4..5eb402986d59 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java @@ -53,7 +53,8 @@ private NetClientAttributesExtractor(NetClientAttributesGetter( getter, diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java index 956873d728aa..0065121a012c 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java @@ -41,7 +41,8 @@ public static ServerAttributesExtractor c /* emitStableUrlAttributes= */ true, /* emitOldHttpAttributes= */ false, // this param does not matter when old semconv is off - InternalServerAttributesExtractor.Mode.HOST); + InternalServerAttributesExtractor.Mode.HOST, + /* captureServerSocketAttributes= */ true); } @Override diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java index 6a3299c82b97..29fdd2577975 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java @@ -26,6 +26,7 @@ public final class InternalServerAttributesExtractor { private final boolean emitStableUrlAttributes; private final boolean emitOldHttpAttributes; private final Mode oldSemconvMode; + private final boolean captureServerSocketAttributes; public InternalServerAttributesExtractor( ServerAttributesGetter getter, @@ -33,13 +34,15 @@ public InternalServerAttributesExtractor( FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitStableUrlAttributes, boolean emitOldHttpAttributes, - Mode oldSemconvMode) { + Mode oldSemconvMode, + boolean captureServerSocketAttributes) { this.getter = getter; this.captureServerPortCondition = captureServerPortCondition; this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitStableUrlAttributes = emitStableUrlAttributes; this.emitOldHttpAttributes = emitOldHttpAttributes; this.oldSemconvMode = oldSemconvMode; + this.captureServerSocketAttributes = captureServerSocketAttributes; } public void onStart(AttributesBuilder attributes, REQUEST request) { @@ -69,7 +72,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO String serverSocketAddress = getter.getServerSocketAddress(request, response); if (serverSocketAddress != null && !serverSocketAddress.equals(serverAddressAndPort.address)) { - if (emitStableUrlAttributes) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_ADDRESS, serverSocketAddress); } if (emitOldHttpAttributes) { @@ -81,7 +84,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO if (serverSocketPort != null && serverSocketPort > 0 && !serverSocketPort.equals(serverAddressAndPort.port)) { - if (emitStableUrlAttributes) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); } if (emitOldHttpAttributes) { @@ -91,7 +94,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO String serverSocketDomain = getter.getServerSocketDomain(request, response); if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddressAndPort.address)) { - if (emitStableUrlAttributes) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); } if (emitOldHttpAttributes && oldSemconvMode.socketDomain != null) { diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java index ab2fcc33f3fd..6fd9fa5fbac7 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java @@ -162,6 +162,7 @@ void normal() { new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java index 8f926a62da85..18b5121b64b6 100644 --- a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java +++ b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java @@ -156,6 +156,7 @@ void normal() { new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java index ea26ec3591ad..75d956f167a9 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java @@ -38,103 +38,119 @@ class HttpServerAttributesExtractorStableSemconvTest { static class TestHttpServerAttributesGetter - implements HttpServerAttributesGetter, Map> { + implements HttpServerAttributesGetter, Map> { @Override - public String getHttpRequestMethod(Map request) { - return (String) request.get("method"); + public String getHttpRequestMethod(Map request) { + return request.get("method"); } @Override - public String getUrlScheme(Map request) { - return (String) request.get("scheme"); + public String getUrlScheme(Map request) { + return request.get("scheme"); } @Nullable @Override - public String getUrlPath(Map request) { - return (String) request.get("path"); + public String getUrlPath(Map request) { + return request.get("path"); } @Nullable @Override - public String getUrlQuery(Map request) { - return (String) request.get("query"); + public String getUrlQuery(Map request) { + return request.get("query"); } @Override - public String getHttpRoute(Map request) { - return (String) request.get("route"); + public String getHttpRoute(Map request) { + return request.get("route"); } @Override - public List getHttpRequestHeader(Map request, String name) { - String values = (String) request.get("header." + name); + public List getHttpRequestHeader(Map request, String name) { + String values = request.get("header." + name); return values == null ? emptyList() : asList(values.split(",")); } @Override public Integer getHttpResponseStatusCode( - Map request, Map response, @Nullable Throwable error) { - String value = (String) response.get("statusCode"); + Map request, Map response, @Nullable Throwable error) { + String value = response.get("statusCode"); return value == null ? null : Integer.parseInt(value); } @Override public List getHttpResponseHeader( - Map request, Map response, String name) { - String values = (String) response.get("header." + name); + Map request, Map response, String name) { + String values = response.get("header." + name); return values == null ? emptyList() : asList(values.split(",")); } } static class TestNetServerAttributesGetter - implements NetServerAttributesGetter, Map> { + implements NetServerAttributesGetter, Map> { @Nullable @Override public String getNetworkTransport( - Map request, @Nullable Map response) { - return (String) request.get("transport"); + Map request, @Nullable Map response) { + return request.get("transport"); } @Nullable @Override public String getNetworkType( - Map request, @Nullable Map response) { - return (String) request.get("type"); + Map request, @Nullable Map response) { + return request.get("type"); } @Nullable @Override public String getNetworkProtocolName( - Map request, Map response) { - return (String) request.get("protocolName"); + Map request, Map response) { + return request.get("protocolName"); } @Nullable @Override public String getNetworkProtocolVersion( - Map request, Map response) { - return (String) request.get("protocolVersion"); + Map request, Map response) { + return request.get("protocolVersion"); } @Nullable @Override - public String getServerAddress(Map request) { - return (String) request.get("hostName"); + public String getServerAddress(Map request) { + return request.get("hostName"); } @Nullable @Override - public Integer getServerPort(Map request) { - return (Integer) request.get("hostPort"); + public Integer getServerPort(Map request) { + String value = request.get("hostPort"); + return value == null ? null : Integer.parseInt(value); + } + + @Nullable + @Override + public String getServerSocketAddress( + Map request, @Nullable Map response) { + return request.get("serverSocketAddress"); + } + + @Nullable + @Override + public Integer getServerSocketPort( + Map request, @Nullable Map response) { + String value = request.get("serverSocketPort"); + return value == null ? null : Integer.parseInt(value); } } @Test void normal() { - Map request = new HashMap<>(); + Map request = new HashMap<>(); request.put("method", "POST"); request.put("url", "http://github.com"); request.put("path", "/repositories/1"); @@ -150,20 +166,23 @@ void normal() { request.put("type", "ipv4"); request.put("protocolName", "http"); request.put("protocolVersion", "2.0"); + request.put("serverSocketAddress", "1.2.3.4"); + request.put("serverSocketPort", "42"); - Map response = new HashMap<>(); + Map response = new HashMap<>(); response.put("statusCode", "202"); response.put("header.content-length", "20"); response.put("header.custom-response-header", "654,321"); Function routeFromContext = ctx -> "/repositories/{repoId}"; - HttpServerAttributesExtractor, Map> extractor = + HttpServerAttributesExtractor, Map> extractor = new HttpServerAttributesExtractor<>( new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); @@ -212,9 +231,8 @@ void skipNetworkTransportIfDefaultForProtocol( request.put("transport", observedTransport); AttributesExtractor, Map> extractor = - HttpClientAttributesExtractor.create( - new HttpClientAttributesExtractorStableSemconvTest.TestHttpClientAttributesGetter(), - new HttpClientAttributesExtractorStableSemconvTest.TestNetClientAttributesGetter()); + HttpServerAttributesExtractor.create( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()); AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, Context.root(), request);