Skip to content

Commit ace20ff

Browse files
authored
Example of how to group related instrumentations under a single module (AwsSdkModule) (#8141)
1 parent a9dc3be commit ace20ff

File tree

5 files changed

+138
-101
lines changed

5 files changed

+138
-101
lines changed

Diff for: dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSHttpClientInstrumentation.java

+1-68
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
import com.amazonaws.AmazonClientException;
1010
import com.amazonaws.Request;
1111
import com.amazonaws.handlers.RequestHandler2;
12-
import com.google.auto.service.AutoService;
1312
import datadog.trace.agent.tooling.Instrumenter;
14-
import datadog.trace.agent.tooling.InstrumenterModule;
1513
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
1614
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
1715
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
@@ -22,26 +20,14 @@
2220
* {@link AmazonClientException} (for example an error thrown by another handler). In these cases
2321
* {@link RequestHandler2#afterError} is not called.
2422
*/
25-
@AutoService(InstrumenterModule.class)
26-
public class AWSHttpClientInstrumentation extends InstrumenterModule.Tracing
23+
public class AWSHttpClientInstrumentation
2724
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
2825

29-
public AWSHttpClientInstrumentation() {
30-
super("aws-sdk");
31-
}
32-
3326
@Override
3427
public String instrumentedType() {
3528
return "com.amazonaws.http.AmazonHttpClient";
3629
}
3730

38-
@Override
39-
public String[] helperClassNames() {
40-
return new String[] {
41-
packageName + ".OnErrorDecorator", packageName + ".AwsNameCache",
42-
};
43-
}
44-
4531
@Override
4632
public void methodAdvice(MethodTransformer transformer) {
4733
transformer.applyAdvice(
@@ -74,57 +60,4 @@ public static void methodExit(
7460
}
7561
}
7662
}
77-
78-
/**
79-
* Due to a change in the AmazonHttpClient class, this instrumentation is needed to support newer
80-
* versions. The above class should cover older versions.
81-
*/
82-
@AutoService(InstrumenterModule.class)
83-
public static final class RequestExecutorInstrumentation extends AWSHttpClientInstrumentation {
84-
85-
@Override
86-
public String instrumentedType() {
87-
return "com.amazonaws.http.AmazonHttpClient$RequestExecutor";
88-
}
89-
90-
@Override
91-
public String[] helperClassNames() {
92-
return new String[] {
93-
packageName + ".OnErrorDecorator", packageName + ".AwsNameCache",
94-
};
95-
}
96-
97-
@Override
98-
public void methodAdvice(MethodTransformer transformer) {
99-
transformer.applyAdvice(
100-
isMethod().and(named("doExecute")),
101-
RequestExecutorInstrumentation.class.getName() + "$RequestExecutorAdvice");
102-
}
103-
104-
public static class RequestExecutorAdvice {
105-
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
106-
public static void methodExit(
107-
@Advice.FieldValue("request") final Request<?> request,
108-
@Advice.Thrown final Throwable throwable) {
109-
110-
final AgentScope scope = activeScope();
111-
// check name in case TracingRequestHandler failed to activate the span
112-
if (scope != null
113-
&& (AwsNameCache.spanName(request).equals(scope.span().getSpanName())
114-
|| scope.span() instanceof AgentTracer.NoopAgentSpan)) {
115-
scope.close();
116-
}
117-
118-
if (throwable != null) {
119-
final AgentSpan span = request.getHandlerContext(SPAN_CONTEXT_KEY);
120-
if (span != null) {
121-
request.addHandlerContext(SPAN_CONTEXT_KEY, null);
122-
DECORATE.onError(span, throwable);
123-
DECORATE.beforeFinish(span);
124-
span.finish();
125-
}
126-
}
127-
}
128-
}
129-
}
13063
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package datadog.trace.instrumentation.aws.v0;
2+
3+
import com.google.auto.service.AutoService;
4+
import datadog.trace.agent.tooling.Instrumenter;
5+
import datadog.trace.agent.tooling.InstrumenterModule;
6+
import java.util.Arrays;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
/** Groups the instrumentations for AWS SDK 1.11.0+. */
11+
@AutoService(InstrumenterModule.class)
12+
public final class AwsSdkModule extends InstrumenterModule.Tracing {
13+
14+
public AwsSdkModule() {
15+
super("aws-sdk");
16+
}
17+
18+
@Override
19+
public String[] helperClassNames() {
20+
return new String[] {
21+
packageName + ".AwsSdkClientDecorator",
22+
packageName + ".GetterAccess",
23+
packageName + ".GetterAccess$1",
24+
packageName + ".TracingRequestHandler",
25+
packageName + ".AwsNameCache",
26+
packageName + ".OnErrorDecorator",
27+
};
28+
}
29+
30+
@Override
31+
public Map<String, String> contextStore() {
32+
Map<String, String> map = new java.util.HashMap<>();
33+
map.put("com.amazonaws.services.sqs.model.ReceiveMessageResult", "java.lang.String");
34+
map.put(
35+
"com.amazonaws.AmazonWebServiceRequest",
36+
"datadog.trace.bootstrap.instrumentation.api.AgentSpan");
37+
return map;
38+
}
39+
40+
@Override
41+
public List<Instrumenter> typeInstrumentations() {
42+
return Arrays.asList(
43+
new AWSHttpClientInstrumentation(),
44+
new RequestExecutorInstrumentation(),
45+
new HandlerChainFactoryInstrumentation());
46+
}
47+
}

Diff for: dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/HandlerChainFactoryInstrumentation.java

+1-30
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,23 @@
44
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
55

66
import com.amazonaws.handlers.RequestHandler2;
7-
import com.google.auto.service.AutoService;
87
import datadog.trace.agent.tooling.Instrumenter;
9-
import datadog.trace.agent.tooling.InstrumenterModule;
108
import datadog.trace.bootstrap.InstrumentationContext;
119
import java.util.List;
12-
import java.util.Map;
1310
import net.bytebuddy.asm.Advice;
1411

1512
/**
1613
* This instrumentation might work with versions before 1.11.0, but this was the first version that
1714
* is tested. It could possibly be extended earlier.
1815
*/
19-
@AutoService(InstrumenterModule.class)
20-
public final class HandlerChainFactoryInstrumentation extends InstrumenterModule.Tracing
16+
public final class HandlerChainFactoryInstrumentation
2117
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
2218

23-
public HandlerChainFactoryInstrumentation() {
24-
super("aws-sdk");
25-
}
26-
2719
@Override
2820
public String instrumentedType() {
2921
return "com.amazonaws.handlers.HandlerChainFactory";
3022
}
3123

32-
@Override
33-
public String[] helperClassNames() {
34-
return new String[] {
35-
packageName + ".AwsSdkClientDecorator",
36-
packageName + ".GetterAccess",
37-
packageName + ".GetterAccess$1",
38-
packageName + ".TracingRequestHandler",
39-
packageName + ".AwsNameCache",
40-
};
41-
}
42-
43-
@Override
44-
public Map<String, String> contextStore() {
45-
Map<String, String> map = new java.util.HashMap<>();
46-
map.put("com.amazonaws.services.sqs.model.ReceiveMessageResult", "java.lang.String");
47-
map.put(
48-
"com.amazonaws.AmazonWebServiceRequest",
49-
"datadog.trace.bootstrap.instrumentation.api.AgentSpan");
50-
return map;
51-
}
52-
5324
@Override
5425
public void methodAdvice(MethodTransformer transformer) {
5526
transformer.applyAdvice(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package datadog.trace.instrumentation.aws.v0;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope;
5+
import static datadog.trace.instrumentation.aws.v0.OnErrorDecorator.DECORATE;
6+
import static datadog.trace.instrumentation.aws.v0.OnErrorDecorator.SPAN_CONTEXT_KEY;
7+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
8+
9+
import com.amazonaws.Request;
10+
import datadog.trace.agent.tooling.Instrumenter;
11+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
12+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
13+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
14+
import net.bytebuddy.asm.Advice;
15+
16+
/**
17+
* Due to a change in the AmazonHttpClient class, this instrumentation is needed to support newer
18+
* versions. The {@link AWSHttpClientInstrumentation} class should cover older versions.
19+
*/
20+
public final class RequestExecutorInstrumentation
21+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
22+
23+
@Override
24+
public String instrumentedType() {
25+
return "com.amazonaws.http.AmazonHttpClient$RequestExecutor";
26+
}
27+
28+
@Override
29+
public void methodAdvice(MethodTransformer transformer) {
30+
transformer.applyAdvice(
31+
isMethod().and(named("doExecute")),
32+
RequestExecutorInstrumentation.class.getName() + "$RequestExecutorAdvice");
33+
}
34+
35+
public static class RequestExecutorAdvice {
36+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
37+
public static void methodExit(
38+
@Advice.FieldValue("request") final Request<?> request,
39+
@Advice.Thrown final Throwable throwable) {
40+
41+
final AgentScope scope = activeScope();
42+
// check name in case TracingRequestHandler failed to activate the span
43+
if (scope != null
44+
&& (AwsNameCache.spanName(request).equals(scope.span().getSpanName())
45+
|| scope.span() instanceof AgentTracer.NoopAgentSpan)) {
46+
scope.close();
47+
}
48+
49+
if (throwable != null) {
50+
final AgentSpan span = request.getHandlerContext(SPAN_CONTEXT_KEY);
51+
if (span != null) {
52+
request.addHandlerContext(SPAN_CONTEXT_KEY, null);
53+
DECORATE.onError(span, throwable);
54+
DECORATE.beforeFinish(span);
55+
span.finish();
56+
}
57+
}
58+
}
59+
}
60+
}

Diff for: docs/how_instrumentations_work.md

+29-3
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ At this point the instrumentation should override the method `muzzleDirective()`
101101

102102
## Instrumentation classes
103103

104-
The Instrumentation class is where the Instrumentation begins. It will:
104+
The Instrumentation class is where the instrumentation begins. It will:
105105

106106
1. Use Matchers to choose target types (i.e., classes)
107107
2. From only those target types, use Matchers to select the members (i.e., methods) to instrument.
@@ -110,8 +110,9 @@ The Instrumentation class is where the Instrumentation begins. It will:
110110
Instrumentation classes:
111111

112112
1. Must be annotated with `@AutoService(InstrumenterModule.class)`
113-
2. Should extend one of the six abstract TargetSystem `InstrumenterModule` classes
114-
3. Should implement one of the `Instrumenter` interfaces
113+
2. Should be declared in a file that ends with `Instrumentation.java`
114+
3. Should extend one of the six abstract TargetSystem `InstrumenterModule` classes
115+
4. Should implement one of the `Instrumenter` interfaces
115116

116117
For example:
117118

@@ -136,6 +137,31 @@ public class RabbitChannelInstrumentation extends InstrumenterModule.Tracing
136137
| `InstrumenterModule.`[`Usm`](https://github.com/DataDog/dd-trace-java/blob/82a3400cd210f4051b92fe1a86cd1b64a17e005e/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java#L273) | |
137138
| [`InstrumenterModule`](https://github.com/DataDog/dd-trace-java/blob/82a3400cd210f4051b92fe1a86cd1b64a17e005e/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java) | Avoid extending `InstrumenterModule` directly. When no other TargetGroup is applicable we generally default to `InstrumenterModule.Tracing` |
138139

140+
### Grouping Instrumentations
141+
142+
Related instrumentations may be grouped under a single `InstrumenterModule` to share common details
143+
such as integration name, helpers, context store use, and optional `classLoaderMatcher()`.
144+
145+
Module classes:
146+
147+
1. Must be annotated with `@AutoService(InstrumenterModule.class)`
148+
2. Should be declared in a file that ends with `Module.java`
149+
3. Should extend one of the six abstract TargetSystem `InstrumenterModule` classes
150+
4. Should have a `typeInstrumentations()` method that returns the instrumentations in the group
151+
5. Should NOT implement one of the `Instrumenter` interfaces
152+
153+
> [!WARNING]
154+
> Grouped instrumentations must NOT be annotated with `@AutoService(InstrumenterModule.class)
155+
> and must NOT extend any of the six abstract TargetSystem `InstrumenterModule` classes
156+
157+
Existing instrumentations can be grouped under a new module, assuming they share the same integration name.
158+
159+
For each member instrumentation:
160+
1. Remove `@AutoService(InstrumenterModule.class)`
161+
2. Remove `extends InstrumenterModule...`
162+
3. Move the list of helpers to the module, merging as necessary
163+
4. Move the context store map to the module, merging as necessary
164+
139165
### Type Matching
140166

141167
Instrumentation classes should implement an

0 commit comments

Comments
 (0)