Skip to content

Commit d3f808e

Browse files
lmolkovatrask
andauthored
Fix azure-core nested HTTP suppression, update libs (#12489)
Co-authored-by: Trask Stalnaker <[email protected]>
1 parent f1c75fa commit d3f808e

File tree

6 files changed

+140
-9
lines changed

6 files changed

+140
-9
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- Update azure-core-tracing-opentelemetry version and improve HTTP suppression to back off
6+
when Azure SDK tracing was disabled.
7+
([#12489](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/12489))
8+
59
## Version 2.9.0 (2024-10-17)
610

711
### 📈 Enhancements

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

+8
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ dependencies {
3333

3434
val latestDepTest = findProperty("testLatestDeps") as Boolean
3535

36+
tasks {
37+
withType<Test>().configureEach {
38+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
39+
}
40+
}
41+
3642
testing {
3743
suites {
3844
// using a test suite to ensure that classes from library-instrumentation-shaded that were
@@ -41,8 +47,10 @@ testing {
4147
dependencies {
4248
if (latestDepTest) {
4349
implementation("com.azure:azure-core:+")
50+
implementation("com.azure:azure-core-test:+")
4451
} else {
4552
implementation("com.azure:azure-core:1.36.0")
53+
implementation("com.azure:azure-core-test:1.16.2")
4654
}
4755
}
4856
}

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

+11-4
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,36 @@ 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(
55+
@Advice.Argument(1) com.azure.core.util.Context azContext,
56+
@Advice.Return(readOnly = false) Mono<HttpResponse> mono) {
57+
mono = disallowNestedClientSpanMono(mono, azContext);
5358
}
5459
}
5560

5661
@SuppressWarnings("unused")
5762
public static class SuppressNestedClientSyncAdvice {
5863

5964
@Advice.OnMethodEnter(suppress = Throwable.class)
60-
public static void syncSendEnter(@Advice.Local("otelScope") Scope scope) {
61-
scope = disallowNestedClientSpanSync();
65+
public static void syncSendEnter(
66+
@Advice.Argument(1) com.azure.core.util.Context azContext,
67+
@Advice.Local("otelScope") Scope scope) {
68+
scope = disallowNestedClientSpanSync(azContext);
6269
}
6370

6471
@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

+8-4
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@
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(
29+
Mono<T> delegate, com.azure.core.util.Context azContext) {
2830
return new Mono<T>() {
2931
@Override
3032
public void subscribe(CoreSubscriber<? super T> coreSubscriber) {
3133
Context parentContext = currentContext();
32-
if (doesNotHaveClientSpan(parentContext)) {
34+
35+
boolean hasAzureClientSpan = azContext.getData("client-method-call-flag").isPresent();
36+
if (doesNotHaveClientSpan(parentContext) && hasAzureClientSpan) {
3337
try (Scope ignored = disallowNestedClientSpan(parentContext).makeCurrent()) {
3438
delegate.subscribe(coreSubscriber);
3539
}

instrumentation/azure-core/azure-core-1.36/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkTest.java

+106
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,35 @@
77

88
import static org.assertj.core.api.Assertions.assertThat;
99

10+
import com.azure.core.annotation.ExpectedResponses;
11+
import com.azure.core.annotation.Get;
12+
import com.azure.core.annotation.Host;
13+
import com.azure.core.annotation.ServiceInterface;
14+
import com.azure.core.http.HttpClient;
15+
import com.azure.core.http.HttpPipeline;
16+
import com.azure.core.http.HttpPipelineBuilder;
17+
import com.azure.core.http.policy.HttpPipelinePolicy;
18+
import com.azure.core.http.policy.HttpPolicyProviders;
19+
import com.azure.core.http.rest.Response;
20+
import com.azure.core.http.rest.RestProxy;
21+
import com.azure.core.test.http.MockHttpResponse;
22+
import com.azure.core.util.ClientOptions;
1023
import com.azure.core.util.Context;
24+
import com.azure.core.util.TracingOptions;
1125
import io.opentelemetry.api.common.Attributes;
1226
import io.opentelemetry.api.trace.SpanKind;
27+
import io.opentelemetry.instrumentation.api.internal.SpanKey;
1328
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
1429
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
1530
import io.opentelemetry.sdk.trace.data.StatusData;
31+
import io.opentelemetry.semconv.HttpAttributes;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.concurrent.atomic.AtomicBoolean;
1635
import org.junit.jupiter.api.Test;
1736
import org.junit.jupiter.api.extension.RegisterExtension;
37+
import reactor.core.publisher.Mono;
38+
import reactor.test.StepVerifier;
1839

1940
class AzureSdkTest {
2041

@@ -48,9 +69,94 @@ void testSpan() {
4869
.hasAttributesSatisfying(Attributes::isEmpty)));
4970
}
5071

72+
@Test
73+
void testPipelineAndSuppression() {
74+
AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false);
75+
76+
HttpClient mockClient =
77+
request ->
78+
Mono.defer(
79+
() -> {
80+
// check if suppression is working
81+
hasClientAndHttpKeys.set(hasClientAndHttpSpans());
82+
return Mono.just(new MockHttpResponse(request, 200));
83+
});
84+
85+
StepVerifier.create(createService(mockClient, true).testMethod())
86+
.expectNextCount(1)
87+
.expectComplete()
88+
.verify();
89+
90+
assertThat(hasClientAndHttpKeys.get()).isTrue();
91+
testing.waitAndAssertTracesWithoutScopeVersionVerification(
92+
trace ->
93+
trace.hasSpansSatisfyingExactly(
94+
span ->
95+
span.hasName("myService.testMethod")
96+
.hasKind(SpanKind.INTERNAL)
97+
.hasStatus(StatusData.unset())
98+
.hasAttributes(Attributes.empty()),
99+
span ->
100+
span.hasKind(SpanKind.CLIENT)
101+
.hasName(Boolean.getBoolean("testLatestDeps") ? "GET" : "HTTP GET")
102+
.hasStatus(StatusData.unset())
103+
.hasAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L)));
104+
}
105+
106+
@Test
107+
void testDisabledTracingNoSuppression() {
108+
AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false);
109+
110+
HttpClient mockClient =
111+
request ->
112+
Mono.defer(
113+
() -> {
114+
// check no suppression
115+
hasClientAndHttpKeys.set(hasClientAndHttpSpans());
116+
return Mono.just(new MockHttpResponse(request, 200));
117+
});
118+
119+
StepVerifier.create(createService(mockClient, false).testMethod())
120+
.expectNextCount(1)
121+
.expectComplete()
122+
.verify();
123+
124+
assertThat(hasClientAndHttpKeys.get()).isFalse();
125+
}
126+
51127
private static com.azure.core.util.tracing.Tracer createAzTracer() {
52128
com.azure.core.util.tracing.TracerProvider azProvider =
53129
com.azure.core.util.tracing.TracerProvider.getDefaultProvider();
54130
return azProvider.createTracer("test-lib", "test-version", "otel.tests", null);
55131
}
132+
133+
private static TestInterface createService(HttpClient httpClient, boolean tracingEnabled) {
134+
List<HttpPipelinePolicy> policies = new ArrayList<>();
135+
HttpPolicyProviders.addAfterRetryPolicies(policies);
136+
137+
ClientOptions clientOptions =
138+
new ClientOptions().setTracingOptions(new TracingOptions().setEnabled(tracingEnabled));
139+
HttpPipeline pipeline =
140+
new HttpPipelineBuilder()
141+
.policies(policies.toArray(new HttpPipelinePolicy[0]))
142+
.httpClient(httpClient)
143+
.clientOptions(clientOptions)
144+
.build();
145+
146+
return RestProxy.create(TestInterface.class, pipeline);
147+
}
148+
149+
private static boolean hasClientAndHttpSpans() {
150+
io.opentelemetry.context.Context ctx = io.opentelemetry.context.Context.current();
151+
return SpanKey.KIND_CLIENT.fromContextOrNull(ctx) != null
152+
&& SpanKey.HTTP_CLIENT.fromContextOrNull(ctx) != null;
153+
}
154+
155+
@Host("https://azure.com")
156+
@ServiceInterface(name = "myService")
157+
interface TestInterface {
158+
@Get("path")
159+
@ExpectedResponses({200})
160+
Mono<Response<Void>> testMethod();
161+
}
56162
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ plugins {
66
group = "io.opentelemetry.javaagent.instrumentation"
77

88
dependencies {
9-
implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.42")
9+
// this is the last good version that works with indy build
10+
// update to 1.49 or latest once https://github.com/Azure/azure-sdk-for-java/pull/42586 is released.
11+
implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.45")
1012
}
1113

1214
tasks {

0 commit comments

Comments
 (0)