Skip to content

Commit 67ee3d7

Browse files
author
Liudmila Molkova
committed
Fix HTTP suppression in azure-core instrumentation
1 parent ca44975 commit 67ee3d7

File tree

16 files changed

+452
-10
lines changed

16 files changed

+452
-10
lines changed

instrumentation/azure-core/azure-core-1.14/javaagent/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
// Ensure no cross interference
3030
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.19:javaagent"))
3131
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.36:javaagent"))
32+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.53:javaagent"))
3233
}
3334

3435
val latestDepTest = findProperty("testLatestDeps") as Boolean

instrumentation/azure-core/azure-core-1.19/javaagent/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
// Ensure no cross interference
3030
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.14:javaagent"))
3131
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.36:javaagent"))
32+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.53:javaagent"))
3233
}
3334

3435
val latestDepTest = findProperty("testLatestDeps") as Boolean

instrumentation/azure-core/azure-core-1.36/javaagent/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ muzzle {
66
pass {
77
group.set("com.azure")
88
module.set("azure-core")
9-
versions.set("[1.36.0,)")
9+
versions.set("[1.36.0,1.53.0)")
1010
assertInverse.set(true)
1111
}
1212
}

instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureHttpClientInstrumentation.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
1313
import static net.bytebuddy.matcher.ElementMatchers.named;
1414
import static net.bytebuddy.matcher.ElementMatchers.returns;
15+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
1516

1617
import com.azure.core.http.HttpResponse;
1718
import io.opentelemetry.context.Scope;
@@ -35,30 +36,32 @@ public void transform(TypeTransformer transformer) {
3536
isMethod()
3637
.and(isPublic())
3738
.and(named("send"))
39+
.and(takesArgument(1, named("com.azure.core.util.Context")))
3840
.and(returns(named("reactor.core.publisher.Mono"))),
3941
this.getClass().getName() + "$SuppressNestedClientMonoAdvice");
4042
transformer.applyAdviceToMethod(
4143
isMethod()
4244
.and(isPublic())
4345
.and(named("sendSync"))
46+
.and(takesArgument(1, named("com.azure.core.util.Context")))
4447
.and(returns(named("com.azure.core.http.HttpResponse"))),
4548
this.getClass().getName() + "$SuppressNestedClientSyncAdvice");
4649
}
4750

4851
@SuppressWarnings("unused")
4952
public static class SuppressNestedClientMonoAdvice {
5053
@Advice.OnMethodExit(suppress = Throwable.class)
51-
public static void asyncSendExit(@Advice.Return(readOnly = false) Mono<HttpResponse> mono) {
52-
mono = disallowNestedClientSpanMono(mono);
54+
public static void asyncSendExit(@Advice.Argument(1) com.azure.core.util.Context azContext, @Advice.Return(readOnly = false) Mono<HttpResponse> mono) {
55+
mono = disallowNestedClientSpanMono(mono, azContext);
5356
}
5457
}
5558

5659
@SuppressWarnings("unused")
5760
public static class SuppressNestedClientSyncAdvice {
5861

5962
@Advice.OnMethodEnter(suppress = Throwable.class)
60-
public static void syncSendEnter(@Advice.Local("otelScope") Scope scope) {
61-
scope = disallowNestedClientSpanSync();
63+
public static void syncSendEnter(@Advice.Argument(1) com.azure.core.util.Context azContext, @Advice.Local("otelScope") Scope scope) {
64+
scope = disallowNestedClientSpanSync(azContext);
6265
}
6366

6467
@Advice.OnMethodExit(suppress = Throwable.class)

instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/SuppressNestedClientHelper.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,23 @@
1616

1717
public class SuppressNestedClientHelper {
1818

19-
public static Scope disallowNestedClientSpanSync() {
19+
public static Scope disallowNestedClientSpanSync(com.azure.core.util.Context azContext) {
2020
Context parentContext = currentContext();
21-
if (doesNotHaveClientSpan(parentContext)) {
21+
boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent();
22+
if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) {
2223
return disallowNestedClientSpan(parentContext).makeCurrent();
2324
}
2425
return null;
2526
}
2627

27-
public static <T> Mono<T> disallowNestedClientSpanMono(Mono<T> delegate) {
28+
public static <T> Mono<T> disallowNestedClientSpanMono(Mono<T> delegate, com.azure.core.util.Context azContext) {
2829
return new Mono<T>() {
2930
@Override
3031
public void subscribe(CoreSubscriber<? super T> coreSubscriber) {
3132
Context parentContext = currentContext();
32-
if (doesNotHaveClientSpan(parentContext)) {
33+
34+
boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent();
35+
if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) {
3336
try (Scope ignored = disallowNestedClientSpan(parentContext).makeCurrent()) {
3437
delegate.subscribe(coreSubscriber);
3538
}

instrumentation/azure-core/azure-core-1.36/library-instrumentation-shaded/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
group = "io.opentelemetry.javaagent.instrumentation"
77

88
dependencies {
9-
implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.42")
9+
implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.49")
1010
}
1111

1212
tasks {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.azure")
8+
module.set("azure-core")
9+
versions.set("[1.53.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
sourceSets {
15+
main {
16+
val shadedDep = project(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded")
17+
output.dir(
18+
shadedDep.file("build/extracted/shadow"),
19+
"builtBy" to ":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded:extractShadowJar"
20+
)
21+
}
22+
}
23+
24+
dependencies {
25+
compileOnly(project(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded", configuration = "shadow"))
26+
27+
library("com.azure:azure-core:1.53.0")
28+
29+
// Ensure no cross interference
30+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.14:javaagent"))
31+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.19:javaagent"))
32+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.36:javaagent"))
33+
}
34+
35+
val latestDepTest = findProperty("testLatestDeps") as Boolean
36+
37+
testing {
38+
suites {
39+
// using a test suite to ensure that classes from library-instrumentation-shaded that were
40+
// extracted to the output directory are not available during tests
41+
val testAzure by registering(JvmTestSuite::class) {
42+
dependencies {
43+
if (latestDepTest) {
44+
implementation("com.azure:azure-core:+")
45+
} else {
46+
implementation("com.azure:azure-core:1.53.0")
47+
}
48+
}
49+
}
50+
}
51+
}
52+
53+
tasks {
54+
check {
55+
dependsOn(testing.suites)
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_36;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
9+
import static io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.SuppressNestedClientHelper.disallowNestedClientSpanMono;
10+
import static io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.SuppressNestedClientHelper.disallowNestedClientSpanSync;
11+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
12+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
13+
import static net.bytebuddy.matcher.ElementMatchers.named;
14+
import static net.bytebuddy.matcher.ElementMatchers.returns;
15+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
16+
17+
import com.azure.core.http.HttpResponse;
18+
import io.opentelemetry.context.Scope;
19+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
20+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
21+
import net.bytebuddy.asm.Advice;
22+
import net.bytebuddy.description.type.TypeDescription;
23+
import net.bytebuddy.matcher.ElementMatcher;
24+
import reactor.core.publisher.Mono;
25+
26+
public class AzureHttpClientInstrumentation implements TypeInstrumentation {
27+
28+
@Override
29+
public ElementMatcher<TypeDescription> typeMatcher() {
30+
return implementsInterface(named("com.azure.core.http.HttpClient"));
31+
}
32+
33+
@Override
34+
public void transform(TypeTransformer transformer) {
35+
transformer.applyAdviceToMethod(
36+
isMethod()
37+
.and(isPublic())
38+
.and(named("send"))
39+
.and(takesArgument(1, named("com.azure.core.util.Context")))
40+
.and(returns(named("reactor.core.publisher.Mono"))),
41+
this.getClass().getName() + "$SuppressNestedClientMonoAdvice");
42+
transformer.applyAdviceToMethod(
43+
isMethod()
44+
.and(isPublic())
45+
.and(named("sendSync"))
46+
.and(takesArgument(1, named("com.azure.core.util.Context")))
47+
.and(returns(named("com.azure.core.http.HttpResponse"))),
48+
this.getClass().getName() + "$SuppressNestedClientSyncAdvice");
49+
}
50+
51+
@SuppressWarnings("unused")
52+
public static class SuppressNestedClientMonoAdvice {
53+
@Advice.OnMethodExit(suppress = Throwable.class)
54+
public static void asyncSendExit(@Advice.Argument(1) com.azure.core.util.Context azContext, @Advice.Return(readOnly = false) Mono<HttpResponse> mono) {
55+
mono = disallowNestedClientSpanMono(mono, azContext);
56+
}
57+
}
58+
59+
@SuppressWarnings("unused")
60+
public static class SuppressNestedClientSyncAdvice {
61+
62+
@Advice.OnMethodEnter(suppress = Throwable.class)
63+
public static void syncSendEnter(@Advice.Argument(1) com.azure.core.util.Context azContext, @Advice.Local("otelScope") Scope scope) {
64+
scope = disallowNestedClientSpanSync(azContext);
65+
}
66+
67+
@Advice.OnMethodExit(suppress = Throwable.class)
68+
public static void syncSendExit(
69+
@Advice.Return HttpResponse response, @Advice.Local("otelScope") Scope scope) {
70+
if (scope != null) {
71+
scope.close();
72+
}
73+
}
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_36;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Arrays.asList;
10+
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
11+
import static net.bytebuddy.matcher.ElementMatchers.not;
12+
13+
import com.google.auto.service.AutoService;
14+
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
15+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
18+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
19+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
20+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
21+
import java.util.List;
22+
import net.bytebuddy.description.type.TypeDescription;
23+
import net.bytebuddy.matcher.ElementMatcher;
24+
25+
@AutoService(InstrumentationModule.class)
26+
public class AzureSdkInstrumentationModule extends InstrumentationModule
27+
implements ExperimentalInstrumentationModule {
28+
public AzureSdkInstrumentationModule() {
29+
super("azure-core", "azure-core-1.36");
30+
}
31+
32+
@Override
33+
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
34+
helperResourceBuilder.register(
35+
"META-INF/services/com.azure.core.util.tracing.TracerProvider",
36+
"azure-core-1.36/META-INF/services/com.azure.core.util.tracing.TracerProvider");
37+
// some azure sdks (e.g. EventHubs) are still looking up Tracer via service loader
38+
// and not yet using the new TracerProvider
39+
helperResourceBuilder.register(
40+
"META-INF/services/com.azure.core.util.tracing.Tracer",
41+
"azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer");
42+
}
43+
44+
@Override
45+
public void injectClasses(ClassInjector injector) {
46+
injector
47+
.proxyBuilder(
48+
"io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracer")
49+
.inject(InjectionMode.CLASS_ONLY);
50+
injector
51+
.proxyBuilder(
52+
"io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracerProvider")
53+
.inject(InjectionMode.CLASS_ONLY);
54+
}
55+
56+
@Override
57+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
58+
// this class was introduced in azure-core 1.36
59+
return hasClassesNamed("com.azure.core.util.tracing.TracerProvider")
60+
.and(not(hasClassesNamed("com.azure.core.tracing.opentelemetry.OpenTelemetryTracer")));
61+
}
62+
63+
@Override
64+
public List<TypeInstrumentation> typeInstrumentations() {
65+
return asList(new EmptyTypeInstrumentation(), new AzureHttpClientInstrumentation());
66+
}
67+
68+
public static class EmptyTypeInstrumentation implements TypeInstrumentation {
69+
@Override
70+
public ElementMatcher<TypeDescription> typeMatcher() {
71+
return namedOneOf(
72+
"com.azure.core.util.tracing.TracerProvider", "com.azure.core.util.tracing.Tracer");
73+
}
74+
75+
@Override
76+
public void transform(TypeTransformer transformer) {
77+
// Nothing to instrument, no methods to match
78+
}
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_36;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
10+
import io.opentelemetry.api.trace.Span;
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.context.Scope;
13+
import io.opentelemetry.instrumentation.api.internal.SpanKey;
14+
import reactor.core.CoreSubscriber;
15+
import reactor.core.publisher.Mono;
16+
17+
public class SuppressNestedClientHelper {
18+
19+
public static Scope disallowNestedClientSpanSync(com.azure.core.util.Context azContext) {
20+
Context parentContext = currentContext();
21+
boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent();
22+
if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) {
23+
return disallowNestedClientSpan(parentContext).makeCurrent();
24+
}
25+
return null;
26+
}
27+
28+
public static <T> Mono<T> disallowNestedClientSpanMono(Mono<T> delegate, com.azure.core.util.Context azContext) {
29+
return new Mono<T>() {
30+
@Override
31+
public void subscribe(CoreSubscriber<? super T> coreSubscriber) {
32+
Context parentContext = currentContext();
33+
34+
boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent();
35+
if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) {
36+
try (Scope ignored = disallowNestedClientSpan(parentContext).makeCurrent()) {
37+
delegate.subscribe(coreSubscriber);
38+
}
39+
} else {
40+
delegate.subscribe(coreSubscriber);
41+
}
42+
}
43+
};
44+
}
45+
46+
private static boolean doesNotHaveClientSpan(Context parentContext) {
47+
return SpanKey.KIND_CLIENT.fromContextOrNull(parentContext) == null
48+
&& SpanKey.HTTP_CLIENT.fromContextOrNull(parentContext) == null;
49+
}
50+
51+
private static Context disallowNestedClientSpan(Context parentContext) {
52+
Span span = Span.getInvalid();
53+
return SpanKey.HTTP_CLIENT.storeInContext(
54+
SpanKey.KIND_CLIENT.storeInContext(parentContext, span), span);
55+
}
56+
57+
private SuppressNestedClientHelper() {}
58+
}

0 commit comments

Comments
 (0)