From cfee5fee4adab393b4af36fa50553b71a49d725d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 20 Oct 2025 18:17:25 -0700 Subject: [PATCH 1/5] Support latest Azure SDK shim --- .../javaagent/build.gradle.kts | 1 + .../javaagent/build.gradle.kts | 1 + .../javaagent/build.gradle.kts | 9 +- .../v1_36/AzureSdkInstrumentationModule.java | 1 + .../build.gradle.kts | 4 +- .../javaagent/build.gradle.kts | 65 +++++++ .../v1_53/AzureHttpClientInstrumentation.java | 80 +++++++++ .../v1_53/AzureSdkInstrumentationModule.java | 85 +++++++++ .../v1_53/SuppressNestedClientHelper.java | 61 +++++++ .../com.azure.core.util.tracing.Tracer | 1 + ...com.azure.core.util.tracing.TracerProvider | 1 + .../azurecore/v1_53/AzureSdkTest.java | 169 ++++++++++++++++++ .../azurecore/v1_53/AzureSdkTestOld.java | 62 +++++++ .../build.gradle.kts | 32 ++++ .../azure-core/azure-core-1.53/metadata.yaml | 5 + settings.gradle.kts | 2 + 16 files changed, 572 insertions(+), 7 deletions(-) create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/build.gradle.kts create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureHttpClientInstrumentation.java create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkInstrumentationModule.java create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/SuppressNestedClientHelper.java create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.Tracer create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.TracerProvider create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java create mode 100644 instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTestOld.java create mode 100644 instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts create mode 100644 instrumentation/azure-core/azure-core-1.53/metadata.yaml diff --git a/instrumentation/azure-core/azure-core-1.14/javaagent/build.gradle.kts b/instrumentation/azure-core/azure-core-1.14/javaagent/build.gradle.kts index 0833f4e53432..0d59e808acbb 100644 --- a/instrumentation/azure-core/azure-core-1.14/javaagent/build.gradle.kts +++ b/instrumentation/azure-core/azure-core-1.14/javaagent/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { // Ensure no cross interference testInstrumentation(project(":instrumentation:azure-core:azure-core-1.19:javaagent")) testInstrumentation(project(":instrumentation:azure-core:azure-core-1.36:javaagent")) + testInstrumentation(project(":instrumentation:azure-core:azure-core-1.53:javaagent")) } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/azure-core/azure-core-1.19/javaagent/build.gradle.kts b/instrumentation/azure-core/azure-core-1.19/javaagent/build.gradle.kts index d2c04ca8e627..d792a81bb5e8 100644 --- a/instrumentation/azure-core/azure-core-1.19/javaagent/build.gradle.kts +++ b/instrumentation/azure-core/azure-core-1.19/javaagent/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { // Ensure no cross interference testInstrumentation(project(":instrumentation:azure-core:azure-core-1.14:javaagent")) testInstrumentation(project(":instrumentation:azure-core:azure-core-1.36:javaagent")) + testInstrumentation(project(":instrumentation:azure-core:azure-core-1.53:javaagent")) } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/build.gradle.kts b/instrumentation/azure-core/azure-core-1.36/javaagent/build.gradle.kts index badc67ffd90d..909e255d662c 100644 --- a/instrumentation/azure-core/azure-core-1.36/javaagent/build.gradle.kts +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/build.gradle.kts @@ -6,7 +6,7 @@ muzzle { pass { group.set("com.azure") module.set("azure-core") - versions.set("[1.36.0,)") + versions.set("[1.36.0,1.53.0)") assertInverse.set(true) } } @@ -29,6 +29,7 @@ dependencies { // Ensure no cross interference testInstrumentation(project(":instrumentation:azure-core:azure-core-1.14:javaagent")) testInstrumentation(project(":instrumentation:azure-core:azure-core-1.19:javaagent")) + testInstrumentation(project(":instrumentation:azure-core:azure-core-1.53:javaagent")) } val latestDepTest = findProperty("testLatestDeps") as Boolean @@ -46,11 +47,11 @@ testing { val testAzure by registering(JvmTestSuite::class) { dependencies { if (latestDepTest) { - implementation("com.azure:azure-core:latest.release") - implementation("com.azure:azure-core-test:latest.release") + implementation("com.azure:azure-core:1.52.0") + implementation("com.azure:azure-core-test:1.26.2") } else { implementation("com.azure:azure-core:1.36.0") - implementation("com.azure:azure-core-test:1.16.2") + implementation("com.azure:azure-core-test:1.14.1") } } } 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 fb8fa767f061..9cae6ccab444 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 @@ -57,6 +57,7 @@ public void injectClasses(ClassInjector injector) { public ElementMatcher.Junction classLoaderMatcher() { // this class was introduced in azure-core 1.36 return hasClassesNamed("com.azure.core.util.tracing.TracerProvider") + .and(not(hasClassesNamed("com.azure.core.util.LibraryTelemetryOptions"))) .and(not(hasClassesNamed("com.azure.core.tracing.opentelemetry.OpenTelemetryTracer"))); } diff --git a/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts b/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts index f3ffaf42e612..b7819f18d081 100644 --- a/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts +++ b/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts @@ -6,9 +6,7 @@ plugins { group = "io.opentelemetry.javaagent.instrumentation" dependencies { - // this is the last good version that works with indy build - // update to 1.49 or latest once https://github.com/Azure/azure-sdk-for-java/pull/42586 is released. - implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.45") + implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.49") } tasks { diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/build.gradle.kts b/instrumentation/azure-core/azure-core-1.53/javaagent/build.gradle.kts new file mode 100644 index 000000000000..f100a1082aec --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("com.azure") + module.set("azure-core") + versions.set("[1.53.0,)") + assertInverse.set(true) + } +} + +sourceSets { + main { + val shadedDep = project(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded") + output.dir( + shadedDep.file("build/extracted/shadow"), + "builtBy" to ":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded:extractShadowJar" + ) + } +} + +dependencies { + compileOnly(project(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded", configuration = "shadow")) + + library("com.azure:azure-core:1.53.0") + + // Ensure no cross interference + testInstrumentation(project(":instrumentation:azure-core:azure-core-1.14:javaagent")) + testInstrumentation(project(":instrumentation:azure-core:azure-core-1.19:javaagent")) + testInstrumentation(project(":instrumentation:azure-core:azure-core-1.36:javaagent")) +} + +val latestDepTest = findProperty("testLatestDeps") as Boolean + +tasks { + withType().configureEach { + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + } +} + +testing { + suites { + // using a test suite to ensure that classes from library-instrumentation-shaded that were + // extracted to the output directory are not available during tests + val testAzure by registering(JvmTestSuite::class) { + dependencies { + if (latestDepTest) { + implementation("com.azure:azure-core:latest.release") + implementation("com.azure:azure-core-test:latest.release") + } else { + implementation("com.azure:azure-core:1.53.0") + implementation("com.azure:azure-core-test:1.26.2") + } + } + } + } +} + +tasks { + check { + dependsOn(testing.suites) + } +} diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureHttpClientInstrumentation.java b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureHttpClientInstrumentation.java new file mode 100644 index 000000000000..d8a497bd6015 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureHttpClientInstrumentation.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.SuppressNestedClientHelper.disallowNestedClientSpanMono; +import static io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.SuppressNestedClientHelper.disallowNestedClientSpanSync; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.azure.core.http.HttpResponse; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import javax.annotation.Nullable; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.Advice.AssignReturned; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import reactor.core.publisher.Mono; + +public class AzureHttpClientInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("com.azure.core.http.HttpClient")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("send")) + .and(takesArgument(1, named("com.azure.core.util.Context"))) + .and(returns(named("reactor.core.publisher.Mono"))), + this.getClass().getName() + "$SuppressNestedClientMonoAdvice"); + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("sendSync")) + .and(takesArgument(1, named("com.azure.core.util.Context"))) + .and(returns(named("com.azure.core.http.HttpResponse"))), + this.getClass().getName() + "$SuppressNestedClientSyncAdvice"); + } + + @SuppressWarnings("unused") + public static class SuppressNestedClientMonoAdvice { + @AssignReturned.ToReturned + @Advice.OnMethodExit(suppress = Throwable.class) + public static Mono asyncSendExit( + @Advice.Argument(1) com.azure.core.util.Context azContext, + @Advice.Return Mono mono) { + return disallowNestedClientSpanMono(mono, azContext); + } + } + + @SuppressWarnings("unused") + public static class SuppressNestedClientSyncAdvice { + + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope syncSendEnter(@Advice.Argument(1) com.azure.core.util.Context azContext) { + return disallowNestedClientSpanSync(azContext); + } + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void syncSendExit(@Advice.Enter @Nullable Scope scope) { + if (scope != null) { + scope.close(); + } + } + } +} diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkInstrumentationModule.java b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkInstrumentationModule.java new file mode 100644 index 000000000000..cd0348f2b6e4 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkInstrumentationModule.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Arrays.asList; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.not; + +import com.google.auto.service.AutoService; +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 io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector; +import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class AzureSdkInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { + public AzureSdkInstrumentationModule() { + super("azure-core", "azure-core-1.53"); + } + + @Override + public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) { + helperResourceBuilder.register( + "META-INF/services/com.azure.core.util.tracing.TracerProvider", + "azure-core-1.53/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.53/META-INF/services/com.azure.core.util.tracing.Tracer"); + } + + @Override + public void injectClasses(ClassInjector injector) { + injector + .proxyBuilder( + "io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracer") + .inject(InjectionMode.CLASS_ONLY); + injector + .proxyBuilder( + "io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracerProvider") + .inject(InjectionMode.CLASS_ONLY); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // LibraryTelemetryOptions was introduced in azure-core 1.53 + return hasClassesNamed("com.azure.core.util.LibraryTelemetryOptions") + .and(not(hasClassesNamed("com.azure.core.tracing.opentelemetry.OpenTelemetryTracer"))); + } + + @Override + public List typeInstrumentations() { + return asList(new EmptyTypeInstrumentation(), new AzureHttpClientInstrumentation()); + } + + public static class EmptyTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return namedOneOf( + "com.azure.core.util.tracing.TracerProvider", "com.azure.core.util.tracing.Tracer"); + } + + @Override + public void transform(TypeTransformer transformer) { + // Nothing to instrument, no methods to match + } + } + + @Override + public boolean isIndyReady() { + return true; + } +} diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/SuppressNestedClientHelper.java b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/SuppressNestedClientHelper.java new file mode 100644 index 000000000000..c6a8f00a6abe --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/SuppressNestedClientHelper.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.internal.SpanKey; +import javax.annotation.Nullable; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.Mono; + +public class SuppressNestedClientHelper { + + @Nullable + public static Scope disallowNestedClientSpanSync(com.azure.core.util.Context azContext) { + Context parentContext = currentContext(); + boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent(); + if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) { + return disallowNestedClientSpan(parentContext).makeCurrent(); + } + return null; + } + + public static Mono disallowNestedClientSpanMono( + Mono delegate, com.azure.core.util.Context azContext) { + return new Mono() { + @Override + public void subscribe(CoreSubscriber coreSubscriber) { + Context parentContext = currentContext(); + + boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent(); + if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) { + try (Scope ignored = disallowNestedClientSpan(parentContext).makeCurrent()) { + delegate.subscribe(coreSubscriber); + } + } else { + delegate.subscribe(coreSubscriber); + } + } + }; + } + + private static boolean doesNotHaveClientSpan(Context parentContext) { + return SpanKey.KIND_CLIENT.fromContextOrNull(parentContext) == null + && SpanKey.HTTP_CLIENT.fromContextOrNull(parentContext) == null; + } + + private static Context disallowNestedClientSpan(Context parentContext) { + Span span = Span.getInvalid(); + return SpanKey.HTTP_CLIENT.storeInContext( + SpanKey.KIND_CLIENT.storeInContext(parentContext, span), span); + } + + private SuppressNestedClientHelper() {} +} diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.Tracer b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.Tracer new file mode 100644 index 000000000000..ac10fa408e6d --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.Tracer @@ -0,0 +1 @@ +io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracer diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.TracerProvider b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.TracerProvider new file mode 100644 index 000000000000..4d7af1762308 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/main/resources/azure-core-1.53/META-INF/services/com.azure.core.util.tracing.TracerProvider @@ -0,0 +1 @@ +io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracerProvider diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java b/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java new file mode 100644 index 000000000000..ca0ac065510c --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java @@ -0,0 +1,169 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static org.assertj.core.api.Assertions.assertThat; + +import com.azure.core.annotation.ExpectedResponses; +import com.azure.core.annotation.Get; +import com.azure.core.annotation.Host; +import com.azure.core.annotation.ServiceInterface; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.http.policy.HttpPolicyProviders; +import com.azure.core.http.rest.Response; +import com.azure.core.http.rest.RestProxy; +import com.azure.core.test.http.MockHttpResponse; +import com.azure.core.util.ClientOptions; +import com.azure.core.util.Context; +import com.azure.core.util.LibraryTelemetryOptions; +import com.azure.core.util.TracingOptions; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.SpanKey; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +class AzureSdkTest { + + @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_53.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()) + .hasAttributesSatisfyingExactly( + equalTo(AttributeKey.stringKey("az.namespace"), "otel.tests")))); + } + + @Test + void testPipelineAndSuppression() { + AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false); + + HttpClient mockClient = + request -> + Mono.defer( + () -> { + // check if suppression is working + hasClientAndHttpKeys.set(hasClientAndHttpSpans()); + return Mono.just(new MockHttpResponse(request, 200)); + }); + + StepVerifier.create(createService(mockClient, true).testMethod()) + .expectNextCount(1) + .expectComplete() + .verify(); + + assertThat(hasClientAndHttpKeys.get()).isTrue(); + testing.waitAndAssertTracesWithoutScopeVersionVerification( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("myService.testMethod") + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributes(Attributes.empty()), + span -> + span.hasKind(SpanKind.CLIENT) + .hasName("GET") + .hasStatus(StatusData.unset()) + .hasAttribute(HTTP_RESPONSE_STATUS_CODE, 200L))); + } + + @Test + void testDisabledTracingNoSuppression() { + AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false); + + HttpClient mockClient = + request -> + Mono.defer( + () -> { + // check no suppression + hasClientAndHttpKeys.set(hasClientAndHttpSpans()); + return Mono.just(new MockHttpResponse(request, 200)); + }); + + StepVerifier.create(createService(mockClient, false).testMethod()) + .expectNextCount(1) + .expectComplete() + .verify(); + + assertThat(hasClientAndHttpKeys.get()).isFalse(); + } + + private static com.azure.core.util.tracing.Tracer createAzTracer() { + com.azure.core.util.tracing.TracerProvider azProvider = + com.azure.core.util.tracing.TracerProvider.getDefaultProvider(); + LibraryTelemetryOptions options = new LibraryTelemetryOptions("test-lib"); + options.setLibraryVersion("test-version"); + options.setResourceProviderNamespace("otel.tests"); + return azProvider.createTracer(options, new TracingOptions()); + } + + private static TestInterface createService(HttpClient httpClient, boolean tracingEnabled) { + List policies = new ArrayList<>(); + HttpPolicyProviders.addAfterRetryPolicies(policies); + + ClientOptions clientOptions = + new ClientOptions().setTracingOptions(new TracingOptions().setEnabled(tracingEnabled)); + HttpPipeline pipeline = + new HttpPipelineBuilder() + .policies(policies.toArray(new HttpPipelinePolicy[0])) + .httpClient(httpClient) + .clientOptions(clientOptions) + .build(); + + return RestProxy.create(TestInterface.class, pipeline); + } + + private static boolean hasClientAndHttpSpans() { + io.opentelemetry.context.Context ctx = io.opentelemetry.context.Context.current(); + return SpanKey.KIND_CLIENT.fromContextOrNull(ctx) != null + && SpanKey.HTTP_CLIENT.fromContextOrNull(ctx) != null; + } + + @Host("https://azure.com") + @ServiceInterface(name = "myService") + interface TestInterface { + @Get("path") + @ExpectedResponses({200}) + Mono> testMethod(); + } +} diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTestOld.java b/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTestOld.java new file mode 100644 index 000000000000..4e617dd5cd75 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTestOld.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53; + +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_53.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()) + .hasAttributes(Attributes.empty()))); + } + + 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; + } +} diff --git a/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts b/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts new file mode 100644 index 000000000000..d4eafb3c2496 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("com.gradleup.shadow") + id("otel.java-conventions") +} + +group = "io.opentelemetry.javaagent.instrumentation" + +dependencies { + // first version of azure-core-tracing-opentelemetry that depends on LibraryTelemetryOptions (azure-core 1.53+) + implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.50") +} + +tasks { + shadowJar { + exclude("META-INF/services/*") + + dependencies { + // including only azure-core-tracing-opentelemetry excludes its transitive dependencies + include(dependency("com.azure:azure-core-tracing-opentelemetry")) + } + relocate( + "com.azure.core.tracing.opentelemetry", + "io.opentelemetry.javaagent.instrumentation.azurecore.v1_53.shaded.com.azure.core.tracing.opentelemetry" + ) + } + + val extractShadowJar by registering(Copy::class) { + dependsOn(shadowJar) + from(zipTree(shadowJar.get().archiveFile)) + into("build/extracted/shadow") + } +} diff --git a/instrumentation/azure-core/azure-core-1.53/metadata.yaml b/instrumentation/azure-core/azure-core-1.53/metadata.yaml new file mode 100644 index 000000000000..87f8d19acab9 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.53/metadata.yaml @@ -0,0 +1,5 @@ +description: This instrumentation enables context propagation for the Azure Core library, it does not emit any telemetry on its own. +features: + - CONTEXT_PROPAGATION + - AUTO_INSTRUMENTATION_SHIM +library_link: https://learn.microsoft.com/en-us/java/api/overview/azure/core-readme?view=azure-java-stable diff --git a/settings.gradle.kts b/settings.gradle.kts index 593700b40dda..47a46f244b71 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -179,6 +179,8 @@ include(":instrumentation:azure-core:azure-core-1.19:javaagent") include(":instrumentation:azure-core:azure-core-1.19:library-instrumentation-shaded") include(":instrumentation:azure-core:azure-core-1.36:javaagent") include(":instrumentation:azure-core:azure-core-1.36:library-instrumentation-shaded") +include(":instrumentation:azure-core:azure-core-1.53:javaagent") +include(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded") include(":instrumentation:c3p0-0.9:javaagent") include(":instrumentation:c3p0-0.9:library") include(":instrumentation:c3p0-0.9:testing") From 85687b90bf5c35a2c530c448b07580a7b23117a7 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Tue, 21 Oct 2025 03:23:52 +0000 Subject: [PATCH 2/5] ./gradlew spotlessApply --- .../instrumentation/azurecore/v1_53/AzureSdkTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java b/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java index ca0ac065510c..8ba863f3bebf 100644 --- a/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java +++ b/instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java @@ -100,11 +100,11 @@ void testPipelineAndSuppression() { .hasKind(SpanKind.INTERNAL) .hasStatus(StatusData.unset()) .hasAttributes(Attributes.empty()), - span -> - span.hasKind(SpanKind.CLIENT) - .hasName("GET") - .hasStatus(StatusData.unset()) - .hasAttribute(HTTP_RESPONSE_STATUS_CODE, 200L))); + span -> + span.hasKind(SpanKind.CLIENT) + .hasName("GET") + .hasStatus(StatusData.unset()) + .hasAttribute(HTTP_RESPONSE_STATUS_CODE, 200L))); } @Test From 7e04ed673965c58c79647216c4f9e3501fae75d1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 20 Oct 2025 20:35:20 -0700 Subject: [PATCH 3/5] fossa --- .fossa.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.fossa.yml b/.fossa.yml index 8e2e16ca330b..0495db599fc7 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -388,6 +388,9 @@ targets: - type: gradle path: ./ target: ':instrumentation:azure-core:azure-core-1.36:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:azure-core:azure-core-1.53:javaagent' - type: gradle path: ./ target: ':instrumentation:cassandra:cassandra-3.0:javaagent' From 56398e123653a7e2545f74342acb716f45559e7b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 21 Oct 2025 08:15:09 -0700 Subject: [PATCH 4/5] fix --- .../library-instrumentation-shaded/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts b/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts index b7819f18d081..a2f56bd6686b 100644 --- a/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts +++ b/instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts @@ -6,7 +6,11 @@ plugins { group = "io.opentelemetry.javaagent.instrumentation" dependencies { - implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.49") + // the latest version that works with azure-core 1.36.0 is 1.0.0-beta.49 + // but the latest version that works with indy build is 1.0.0-beta.45 + // (indy build was fixed by https://github.com/Azure/azure-sdk-for-java/pull/42586 + // in 1.0.0-beta.51) + implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.45") } tasks { From 357b0c08cd2e8f53dc3aaaa24d494759c2de7d7e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 21 Oct 2025 09:42:23 -0700 Subject: [PATCH 5/5] fix --- .../library-instrumentation-shaded/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts b/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts index d4eafb3c2496..28a979116e93 100644 --- a/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts +++ b/instrumentation/azure-core/azure-core-1.53/library-instrumentation-shaded/build.gradle.kts @@ -6,8 +6,7 @@ plugins { group = "io.opentelemetry.javaagent.instrumentation" dependencies { - // first version of azure-core-tracing-opentelemetry that depends on LibraryTelemetryOptions (azure-core 1.53+) - implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.50") + implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.61") } tasks {