Skip to content

Commit a756ca5

Browse files
committed
Fix azure-core instrumentation, including indy
1 parent 7d902f5 commit a756ca5

5 files changed

Lines changed: 135 additions & 1 deletion

File tree

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ muzzle {
88
module.set("azure-core")
99
versions.set("[1.53.0,)")
1010
assertInverse.set(true)
11+
// our advice helper bridges an explicitly supplied application parent context to the agent
12+
// context, so it references io.opentelemetry.context.{Context,Scope}
13+
extraDependency("io.opentelemetry:opentelemetry-api:1.27.0")
1114
}
1215
}
1316

@@ -24,6 +27,10 @@ sourceSets {
2427
dependencies {
2528
compileOnly(project(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded", configuration = "shadow"))
2629

30+
// needed to bridge an explicitly supplied application parent context (the unshaded
31+
// "application.io.opentelemetry.*" types) to the agent context inside our advice
32+
compileOnly(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow"))
33+
2734
library("com.azure:azure-core:1.53.0")
2835

2936
// Ensure no cross interference
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
10+
11+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
13+
import java.util.Optional;
14+
import net.bytebuddy.asm.Advice;
15+
import net.bytebuddy.asm.Advice.AssignReturned;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
import net.bytebuddy.matcher.ElementMatcher;
18+
19+
/**
20+
* Bridges an explicitly supplied parent context for {@code azure-core-tracing-opentelemetry}.
21+
*
22+
* <p>When a user passes a parent context to an Azure SDK call, the value is stored on the {@link
23+
* com.azure.core.util.Context} under the {@code "trace-context"} key as the application's (unshaded)
24+
* {@code io.opentelemetry.context.Context}. The bundled {@code OpenTelemetryTracer} reads that value
25+
* back and expects it to be the agent's (shaded) context. Convert it here so the tracer does not
26+
* need to reach into agent internals reflectively.
27+
*/
28+
class AzureContextInstrumentation implements TypeInstrumentation {
29+
30+
@Override
31+
public ElementMatcher<TypeDescription> typeMatcher() {
32+
return named("com.azure.core.util.Context");
33+
}
34+
35+
@Override
36+
public void transform(TypeTransformer transformer) {
37+
transformer.applyAdviceToMethod(
38+
named("getData").and(takesArguments(1)),
39+
getClass().getName() + "$GetDataAdvice");
40+
}
41+
42+
@SuppressWarnings("unused")
43+
public static class GetDataAdvice {
44+
@AssignReturned.ToReturned
45+
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
46+
public static Optional<Object> onExit(
47+
@Advice.Argument(0) Object key, @Advice.Return Optional<Object> data) {
48+
return AzureExplicitParentContextHelper.bridgeApplicationContext(key, data);
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53;
7+
8+
import io.opentelemetry.context.Context;
9+
import java.util.Optional;
10+
11+
/**
12+
* Converts an explicitly supplied application parent context into the agent context.
13+
*
14+
* <p>In the agent the application's {@code io.opentelemetry.context.Context} (referenced here as
15+
* {@code application.io.opentelemetry.context.Context}) is bridged to the agent context by the
16+
* opentelemetry-api instrumentation. Making the application context current and reading back the
17+
* agent context performs that conversion using only public API, so this works in both inline and
18+
* indy mode without reaching into agent-internal helper classes.
19+
*/
20+
public final class AzureExplicitParentContextHelper {
21+
22+
// azure-core-tracing-opentelemetry stores the explicit parent context under this key
23+
private static final String PARENT_TRACE_CONTEXT_KEY = "trace-context";
24+
25+
public static Optional<Object> bridgeApplicationContext(Object key, Optional<Object> data) {
26+
if (!PARENT_TRACE_CONTEXT_KEY.equals(key) || data == null || !data.isPresent()) {
27+
return data;
28+
}
29+
Object value = data.get();
30+
if (!(value instanceof application.io.opentelemetry.context.Context)) {
31+
return data;
32+
}
33+
application.io.opentelemetry.context.Context applicationContext =
34+
(application.io.opentelemetry.context.Context) value;
35+
Context agentContext;
36+
try (application.io.opentelemetry.context.Scope ignored = applicationContext.makeCurrent()) {
37+
agentContext = Context.current();
38+
}
39+
return Optional.of(agentContext);
40+
}
41+
42+
private AzureExplicitParentContextHelper() {}
43+
}

instrumentation/azure-core/azure-core-1.53/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkInstrumentationModule.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
5757

5858
@Override
5959
public List<TypeInstrumentation> typeInstrumentations() {
60-
return asList(new EmptyTypeInstrumentation(), new AzureHttpClientInstrumentation());
60+
return asList(
61+
new EmptyTypeInstrumentation(),
62+
new AzureContextInstrumentation(),
63+
new AzureHttpClientInstrumentation());
6164
}
6265

6366
private static class EmptyTypeInstrumentation implements TypeInstrumentation {

instrumentation/azure-core/azure-core-1.53/javaagent/src/testAzure/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_53/AzureSdkTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import com.azure.core.util.TracingOptions;
3333
import com.azure.core.util.tracing.Tracer;
3434
import com.azure.core.util.tracing.TracerProvider;
35+
import io.opentelemetry.api.GlobalOpenTelemetry;
36+
import io.opentelemetry.api.trace.Span;
3537
import io.opentelemetry.api.trace.SpanKind;
3638
import io.opentelemetry.instrumentation.api.internal.SpanKey;
3739
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
@@ -78,6 +80,34 @@ void testSpan() {
7880
equalTo(stringKey("az.namespace"), "otel.tests"))));
7981
}
8082

83+
@Test
84+
void testExplicitParentContextBridge() {
85+
// Azure's bundled OpenTelemetryTracer reflectively calls
86+
// io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.AgentContextStorage
87+
// #getAgentContext(application io.opentelemetry.context.Context)
88+
// to convert an explicitly-supplied application context into the agent (shaded) context.
89+
// This test exercises that path: the parent span is supplied only via the Azure context bag
90+
// under PARENT_TRACE_CONTEXT_KEY and is never made current, so the only way the child can be
91+
// linked to it is through the reflective bridge.
92+
Tracer azTracer = createAzTracer();
93+
94+
Span parentSpan = GlobalOpenTelemetry.getTracer("test").spanBuilder("parent").startSpan();
95+
// application (unshaded) context carrying the parent span, NOT made current
96+
io.opentelemetry.context.Context parentContext =
97+
io.opentelemetry.context.Context.root().with(parentSpan);
98+
99+
Context azContext = new Context(Tracer.PARENT_TRACE_CONTEXT_KEY, parentContext);
100+
Context child = azTracer.start("child", azContext);
101+
azTracer.end(null, null, child);
102+
parentSpan.end();
103+
104+
testing.waitAndAssertTracesWithoutScopeVersionVerification(
105+
trace ->
106+
trace.hasSpansSatisfyingExactly(
107+
span -> span.hasName("parent").hasNoParent(),
108+
span -> span.hasName("child").hasParent(trace.getSpan(0))));
109+
}
110+
81111
@Test
82112
void testPipelineAndSuppression() {
83113
AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false);

0 commit comments

Comments
 (0)