Skip to content

Commit 6d86370

Browse files
authored
Create S3 instrumentation + add span pointers (#8075)
1 parent 523df01 commit 6d86370

File tree

26 files changed

+808
-37
lines changed

26 files changed

+808
-37
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
muzzle {
2+
pass {
3+
group = "software.amazon.awssdk"
4+
module = "s3"
5+
versions = "[2,3)"
6+
assertInverse = true
7+
}
8+
}
9+
10+
apply from: "$rootDir/gradle/java.gradle"
11+
12+
addTestSuiteForDir('latestDepTest', 'test')
13+
addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test')
14+
15+
dependencies {
16+
compileOnly group: 'software.amazon.awssdk', name: 's3', version: '2.29.26'
17+
18+
// Include httpclient instrumentation for testing because it is a dependency for aws-sdk.
19+
testRuntimeOnly project(':dd-java-agent:instrumentation:apache-httpclient-4')
20+
testRuntimeOnly project(':dd-java-agent:instrumentation:aws-java-sdk-2.2')
21+
testImplementation 'software.amazon.awssdk:s3:2.29.26'
22+
testImplementation 'org.testcontainers:localstack:1.20.1'
23+
24+
latestDepTestImplementation group: 'software.amazon.awssdk', name: 's3', version: '+'
25+
}
26+
27+
tasks.withType(Test).configureEach {
28+
usesService(testcontainersLimit)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package datadog.trace.instrumentation.aws.v2.s3;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
5+
6+
import com.google.auto.service.AutoService;
7+
import datadog.trace.agent.tooling.Instrumenter;
8+
import datadog.trace.agent.tooling.InstrumenterModule;
9+
import java.util.List;
10+
import net.bytebuddy.asm.Advice;
11+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
12+
13+
@AutoService(InstrumenterModule.class)
14+
public final class S3ClientInstrumentation extends InstrumenterModule.Tracing
15+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
16+
public S3ClientInstrumentation() {
17+
super("s3", "aws-s3");
18+
}
19+
20+
@Override
21+
public String instrumentedType() {
22+
return "software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder";
23+
}
24+
25+
@Override
26+
public void methodAdvice(MethodTransformer transformer) {
27+
transformer.applyAdvice(
28+
isMethod().and(named("resolveExecutionInterceptors")),
29+
S3ClientInstrumentation.class.getName() + "$AwsS3BuilderAdvice");
30+
}
31+
32+
@Override
33+
public String[] helperClassNames() {
34+
return new String[] {packageName + ".S3Interceptor", packageName + ".TextMapInjectAdapter"};
35+
}
36+
37+
public static class AwsS3BuilderAdvice {
38+
@Advice.OnMethodExit(suppress = Throwable.class)
39+
public static void addHandler(@Advice.Return final List<ExecutionInterceptor> interceptors) {
40+
for (ExecutionInterceptor interceptor : interceptors) {
41+
if (interceptor instanceof S3Interceptor) {
42+
return; // list already has our interceptor, return to builder
43+
}
44+
}
45+
interceptors.add(new S3Interceptor());
46+
}
47+
}
48+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package datadog.trace.instrumentation.aws.v2.s3;
2+
3+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.S3_ETAG;
4+
5+
import datadog.trace.api.Config;
6+
import datadog.trace.bootstrap.InstanceStore;
7+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
import software.amazon.awssdk.core.interceptor.Context;
11+
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
12+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
13+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
14+
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse;
15+
import software.amazon.awssdk.services.s3.model.CopyObjectResponse;
16+
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
17+
18+
public class S3Interceptor implements ExecutionInterceptor {
19+
private static final Logger log = LoggerFactory.getLogger(S3Interceptor.class);
20+
21+
public static final ExecutionAttribute<AgentSpan> SPAN_ATTRIBUTE =
22+
InstanceStore.of(ExecutionAttribute.class)
23+
.putIfAbsent("DatadogSpan", () -> new ExecutionAttribute<>("DatadogSpan"));
24+
25+
private static final boolean CAN_ADD_SPAN_POINTERS = Config.get().isAddSpanPointers("aws");
26+
27+
@Override
28+
public void afterExecution(
29+
Context.AfterExecution context, ExecutionAttributes executionAttributes) {
30+
if (!CAN_ADD_SPAN_POINTERS) {
31+
return;
32+
}
33+
34+
AgentSpan span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
35+
if (span == null) {
36+
log.debug("Unable to find S3 request span. Not creating span pointer.");
37+
return;
38+
}
39+
String eTag;
40+
Object response = context.response();
41+
42+
// Get eTag for hash calculation.
43+
// https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html
44+
if (response instanceof PutObjectResponse) {
45+
eTag = ((PutObjectResponse) response).eTag();
46+
} else if (response instanceof CopyObjectResponse) {
47+
eTag = ((CopyObjectResponse) response).copyObjectResult().eTag();
48+
} else if (response instanceof CompleteMultipartUploadResponse) {
49+
eTag = ((CompleteMultipartUploadResponse) response).eTag();
50+
} else {
51+
return;
52+
}
53+
54+
// Store eTag as tag, then calculate hash + add span pointers in SpanPointersProcessor.
55+
// Bucket and key are already stored as tags in AwsSdkClientDecorator, so need to make redundant
56+
// tags.
57+
span.setTag(S3_ETAG, eTag);
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package datadog.trace.instrumentation.aws.v2.s3;
2+
3+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
4+
5+
public class TextMapInjectAdapter implements AgentPropagation.Setter<StringBuilder> {
6+
7+
public static final TextMapInjectAdapter SETTER = new TextMapInjectAdapter();
8+
9+
@Override
10+
public void set(final StringBuilder builder, final String key, final String value) {
11+
builder.append('"').append(key).append("\":\"").append(value).append("\",");
12+
}
13+
}

0 commit comments

Comments
 (0)