Skip to content

Commit 2575c71

Browse files
authored
Create DynamoDB instrumentation + add span pointers for updateItem and deleteItem (#8490)
1 parent b34cd6d commit 2575c71

File tree

11 files changed

+936
-28
lines changed

11 files changed

+936
-28
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 = "dynamodb"
5+
versions = "[2.0,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: 'dynamodb', version: '2.30.22'
17+
18+
// Include httpclient instrumentation for testing because it is a dependency for aws-sdk.
19+
testImplementation project(':dd-java-agent:instrumentation:apache-httpclient-4')
20+
testImplementation project(':dd-java-agent:instrumentation:aws-java-sdk-2.2')
21+
testImplementation 'software.amazon.awssdk:dynamodb:2.30.22'
22+
testImplementation 'org.testcontainers:localstack:1.20.1'
23+
24+
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'dynamodb', 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.dynamodb;
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 DynamoDbClientInstrumentation extends InstrumenterModule.Tracing
15+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
16+
public DynamoDbClientInstrumentation() {
17+
super("dynamodb", "aws-dynamodb");
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+
DynamoDbClientInstrumentation.class.getName() + "$AwsDynamoDbBuilderAdvice");
30+
}
31+
32+
@Override
33+
public String[] helperClassNames() {
34+
return new String[] {packageName + ".DynamoDbInterceptor", packageName + ".DynamoDbUtil"};
35+
}
36+
37+
public static class AwsDynamoDbBuilderAdvice {
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 DynamoDbInterceptor) {
42+
return; // list already has our interceptor, return to builder
43+
}
44+
}
45+
interceptors.add(new DynamoDbInterceptor());
46+
}
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package datadog.trace.instrumentation.aws.v2.dynamodb;
2+
3+
import datadog.trace.api.Config;
4+
import datadog.trace.bootstrap.InstanceStore;
5+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
6+
import java.util.Map;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import software.amazon.awssdk.core.SdkRequest;
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.dynamodb.model.AttributeValue;
15+
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
16+
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
17+
18+
public class DynamoDbInterceptor implements ExecutionInterceptor {
19+
private static final Logger log = LoggerFactory.getLogger(DynamoDbInterceptor.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 DynamoDb request span. Not creating span pointer.");
37+
return;
38+
}
39+
40+
SdkRequest request = context.request();
41+
if (request instanceof UpdateItemRequest) {
42+
Map<String, AttributeValue> keys = ((UpdateItemRequest) request).key();
43+
DynamoDbUtil.exportTagsWithKnownKeys(span, keys);
44+
} else if (request instanceof DeleteItemRequest) {
45+
Map<String, AttributeValue> keys = ((DeleteItemRequest) request).key();
46+
DynamoDbUtil.exportTagsWithKnownKeys(span, keys);
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package datadog.trace.instrumentation.aws.v2.dynamodb;
2+
3+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DYNAMO_PRIMARY_KEY_1;
4+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DYNAMO_PRIMARY_KEY_1_VALUE;
5+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DYNAMO_PRIMARY_KEY_2;
6+
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DYNAMO_PRIMARY_KEY_2_VALUE;
7+
8+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.List;
12+
import java.util.Map;
13+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
14+
15+
public class DynamoDbUtil {
16+
/**
17+
* Extracts a string value from a DynamoDB AttributeValue.
18+
*
19+
* @param value The AttributeValue to extract from
20+
* @return The extracted string value
21+
*/
22+
private static String extractValueAsString(AttributeValue value) {
23+
if (value == null) {
24+
return "";
25+
}
26+
27+
if (value.s() != null) {
28+
return value.s();
29+
} else if (value.n() != null) {
30+
return value.n();
31+
} else if (value.b() != null) {
32+
// For binary values, convert the bytes back to string
33+
return new String(value.b().asByteArray(), java.nio.charset.Charset.defaultCharset());
34+
}
35+
36+
return "";
37+
}
38+
39+
/**
40+
* Gets primary key/values and exports them as temporary tags on the span so that
41+
* SpanPointersProcessor.java can complete the span pointer creation.
42+
*
43+
* @param span The span to set the temporary tags on
44+
* @param keys The primary key/values to extract from
45+
*/
46+
static void exportTagsWithKnownKeys(AgentSpan span, Map<String, AttributeValue> keys) {
47+
if (keys == null || keys.isEmpty()) {
48+
return;
49+
}
50+
51+
if (keys.size() == 1) {
52+
// Single primary key case
53+
Map.Entry<String, AttributeValue> entry = keys.entrySet().iterator().next();
54+
span.setTag(DYNAMO_PRIMARY_KEY_1, entry.getKey());
55+
span.setTag(DYNAMO_PRIMARY_KEY_1_VALUE, extractValueAsString(entry.getValue()));
56+
} else {
57+
// Sort keys alphabetically
58+
List<String> keyNames = new ArrayList<>(keys.keySet());
59+
Collections.sort(keyNames);
60+
61+
// First key (alphabetically)
62+
String primaryKey1Name = keyNames.get(0);
63+
span.setTag(DYNAMO_PRIMARY_KEY_1, primaryKey1Name);
64+
span.setTag(DYNAMO_PRIMARY_KEY_1_VALUE, extractValueAsString(keys.get(primaryKey1Name)));
65+
66+
// Second key
67+
String primaryKey2Name = keyNames.get(1);
68+
span.setTag(DYNAMO_PRIMARY_KEY_2, primaryKey2Name);
69+
span.setTag(DYNAMO_PRIMARY_KEY_2_VALUE, extractValueAsString(keys.get(primaryKey2Name)));
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)