Skip to content

Commit ab9169c

Browse files
authored
Add Azure SDK instrumentation (#5467)
* Add Azure SDK instrumentation * Add to supported libraries table * Keep suppression for 1.19
1 parent 47f2732 commit ab9169c

File tree

18 files changed

+530
-0
lines changed

18 files changed

+530
-0
lines changed

docs/supported-libraries.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ These are the supported libraries and frameworks:
4242
| [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client) | 1.9+ |
4343
| [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html) | 1.0+ |
4444
| [AWS SDK](https://aws.amazon.com/sdk-for-java/) | 1.11.x and 2.2.0+ |
45+
| [Azure Core](https://docs.microsoft.com/en-us/java/api/overview/azure/core-readme) | 1.14+ |
4546
| [Cassandra Driver](https://github.com/datastax/java-driver) | 3.0+ |
4647
| [Couchbase Client](https://github.com/couchbase/couchbase-java-client) | 2.0+ and 3.1+ |
4748
| [Dropwizard Views](https://www.dropwizard.io/en/latest/manual/views.html) | 0.7+ |
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.14.0,1.19.0)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
sourceSets {
15+
main {
16+
val shadedDep = project(":instrumentation:azure-core:azure-core-1.14:library-instrumentation-shaded")
17+
output.dir(shadedDep.file("build/extracted/shadow"), "builtBy" to ":instrumentation:azure-core:azure-core-1.14:library-instrumentation-shaded:extractShadowJar")
18+
}
19+
}
20+
21+
dependencies {
22+
compileOnly(project(path = ":instrumentation:azure-core:azure-core-1.14:library-instrumentation-shaded", configuration = "shadow"))
23+
24+
library("com.azure:azure-core:1.14.0")
25+
26+
// Ensure no cross interference
27+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.19:javaagent"))
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_14;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.returns;
13+
14+
import com.azure.core.http.HttpResponse;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import net.bytebuddy.asm.Advice;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
import reactor.core.publisher.Mono;
21+
22+
public class AzureHttpClientInstrumentation implements TypeInstrumentation {
23+
24+
@Override
25+
public ElementMatcher<TypeDescription> typeMatcher() {
26+
return implementsInterface(named("com.azure.core.http.HttpClient"));
27+
}
28+
29+
@Override
30+
public void transform(TypeTransformer transformer) {
31+
transformer.applyAdviceToMethod(
32+
isMethod()
33+
.and(isPublic())
34+
.and(named("send"))
35+
.and(returns(named("reactor.core.publisher.Mono"))),
36+
this.getClass().getName() + "$SuppressNestedClientAdvice");
37+
}
38+
39+
public static class SuppressNestedClientAdvice {
40+
41+
@Advice.OnMethodExit(suppress = Throwable.class)
42+
public static void methodExit(@Advice.Return(readOnly = false) Mono<HttpResponse> mono) {
43+
mono = new SuppressNestedClientMono<>(mono);
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_14;
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.named;
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 java.util.List;
19+
import net.bytebuddy.description.type.TypeDescription;
20+
import net.bytebuddy.matcher.ElementMatcher;
21+
22+
@AutoService(InstrumentationModule.class)
23+
public class AzureSdkInstrumentationModule extends InstrumentationModule {
24+
public AzureSdkInstrumentationModule() {
25+
super("azure-core", "azure-core-1.14");
26+
}
27+
28+
@Override
29+
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
30+
helperResourceBuilder.register(
31+
"META-INF/services/com.azure.core.http.policy.AfterRetryPolicyProvider",
32+
"azure-core-1.14/META-INF/services/com.azure.core.http.policy.AfterRetryPolicyProvider");
33+
helperResourceBuilder.register(
34+
"META-INF/services/com.azure.core.util.tracing.Tracer",
35+
"azure-core-1.14/META-INF/services/com.azure.core.util.tracing.Tracer");
36+
}
37+
38+
@Override
39+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
40+
return hasClassesNamed("com.azure.core.util.tracing.Tracer")
41+
// this is needed to prevent this instrumentation from being applied to azure-core 1.19+
42+
.and(not(hasClassesNamed("com.azure.core.util.tracing.StartSpanOptions")))
43+
.and(not(hasClassesNamed("com.azure.core.tracing.opentelemetry.OpenTelemetryTracer")));
44+
}
45+
46+
@Override
47+
public List<TypeInstrumentation> typeInstrumentations() {
48+
return asList(new EmptyTypeInstrumentation(), new AzureHttpClientInstrumentation());
49+
}
50+
51+
public static class EmptyTypeInstrumentation implements TypeInstrumentation {
52+
@Override
53+
public ElementMatcher<TypeDescription> typeMatcher() {
54+
// we cannot use com.azure.core.http.policy.AfterRetryPolicyProvider
55+
// or com.azure.core.util.tracing.Tracer here because we inject classes that implement these
56+
// interfaces, causing the first one of these interfaces to be transformed to cause itself to
57+
// be loaded (again), which leads to duplicate class definition error after the interface is
58+
// transformed and the triggering class loader tries to load it.
59+
//
60+
// this is a list of all classes that call one of these:
61+
// * ServiceLoader.load(AfterRetryPolicyProvider.class)
62+
// * ServiceLoader.load(Tracer.class)
63+
return named("com.azure.core.http.policy.HttpPolicyProviders")
64+
.or(named("com.azure.core.util.tracing.TracerProxy"))
65+
.or(named("com.azure.cosmos.CosmosAsyncClient"))
66+
.or(named("com.azure.messaging.eventhubs.EventHubClientBuilder"))
67+
.or(named("com.azure.messaging.eventhubs.EventProcessorClientBuilder"))
68+
.or(named("com.azure.messaging.servicebus.ServiceBusClientBuilder"));
69+
}
70+
71+
@Override
72+
public void transform(TypeTransformer transformer) {
73+
// Nothing to instrument, no methods to match
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.azurecore.v1_14;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.api.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 SuppressNestedClientMono<T> extends Mono<T> {
18+
19+
private final Mono<T> delegate;
20+
21+
public SuppressNestedClientMono(Mono<T> delegate) {
22+
this.delegate = delegate;
23+
}
24+
25+
@Override
26+
public void subscribe(CoreSubscriber<? super T> actual) {
27+
Context parentContext = currentContext();
28+
if (SpanKey.HTTP_CLIENT.fromContextOrNull(parentContext) == null) {
29+
try (Scope ignored =
30+
SpanKey.HTTP_CLIENT.storeInContext(parentContext, Span.getInvalid()).makeCurrent()) {
31+
delegate.subscribe(actual);
32+
}
33+
} else {
34+
delegate.subscribe(actual);
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.opentelemetry.javaagent.instrumentation.azurecore.v1_14.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryHttpPolicy
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.opentelemetry.javaagent.instrumentation.azurecore.v1_14.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracer
Lines changed: 43 additions & 0 deletions
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+
import com.azure.core.http.policy.HttpPolicyProviders
7+
import com.azure.core.util.Context
8+
import com.azure.core.util.tracing.TracerProxy
9+
import io.opentelemetry.api.trace.StatusCode
10+
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
11+
12+
class AzureSdkTest extends AgentInstrumentationSpecification {
13+
14+
def "test helper classes injected"() {
15+
expect:
16+
TracerProxy.isTracingEnabled()
17+
18+
def list = new ArrayList()
19+
HttpPolicyProviders.addAfterRetryPolicies(list)
20+
21+
list.size() == 1
22+
list.get(0).getClass().getName() == "io.opentelemetry.javaagent.instrumentation.azurecore.v1_14.shaded" +
23+
".com.azure.core.tracing.opentelemetry.OpenTelemetryHttpPolicy"
24+
}
25+
26+
def "test span"() {
27+
when:
28+
Context context = TracerProxy.start("hello", Context.NONE)
29+
TracerProxy.end(200, null, context)
30+
31+
then:
32+
assertTraces(1) {
33+
trace(0, 1) {
34+
span(0) {
35+
name "hello"
36+
status StatusCode.OK
37+
attributes {
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id("com.github.johnrengelman.shadow")
3+
4+
id("otel.java-conventions")
5+
}
6+
7+
group = "io.opentelemetry.javaagent.instrumentation"
8+
9+
dependencies {
10+
// this is the latest version that works with azure-core 1.14
11+
implementation("com.azure:azure-core-tracing-opentelemetry:1.0.0-beta.12")
12+
}
13+
14+
tasks {
15+
shadowJar {
16+
exclude("META-INF/services/*")
17+
18+
dependencies {
19+
// including only azure-core-tracing-opentelemetry excludes its transitive dependencies
20+
include(dependency("com.azure:azure-core-tracing-opentelemetry"))
21+
}
22+
relocate("com.azure.core.tracing.opentelemetry", "io.opentelemetry.javaagent.instrumentation.azurecore.v1_14.shaded.com.azure.core.tracing.opentelemetry")
23+
}
24+
25+
val extractShadowJar by registering(Copy::class) {
26+
dependsOn(shadowJar)
27+
from(zipTree(shadowJar.get().archiveFile))
28+
into("build/extracted/shadow")
29+
}
30+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.19.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
sourceSets {
15+
main {
16+
val shadedDep = project(":instrumentation:azure-core:azure-core-1.19:library-instrumentation-shaded")
17+
output.dir(shadedDep.file("build/extracted/shadow"), "builtBy" to ":instrumentation:azure-core:azure-core-1.19:library-instrumentation-shaded:extractShadowJar")
18+
}
19+
}
20+
21+
dependencies {
22+
compileOnly(project(path = ":instrumentation:azure-core:azure-core-1.19:library-instrumentation-shaded", configuration = "shadow"))
23+
24+
library("com.azure:azure-core:1.19.0")
25+
26+
// Ensure no cross interference
27+
testInstrumentation(project(":instrumentation:azure-core:azure-core-1.14:javaagent"))
28+
}

0 commit comments

Comments
 (0)