From 78c738ee5e77277168a8b3f8d80fa58f6c9eac44 Mon Sep 17 00:00:00 2001 From: Pedram Safaei Date: Tue, 22 Sep 2026 09:24:24 +0000 Subject: [PATCH] Add AWS resource ARN tags to AWS SDK client spans The AWS SDK v1 and v2 instrumentations already read several resource identifiers out of the request to derive peer.service, but only emitted the short names. The ARN itself, which carries the partition, Region and owning account, was dropped even though it was already in hand. This tags AWS SDK client spans with the complete identity present in the request: - SNS: aws.topic.arn and aws.sns.topic_arn (full TopicArn / TargetArn), in addition to the existing aws.topic.name. dd-trace-py and dd-trace-js already emit aws.sns.topic_arn, so the same query now works across tracers. - Kinesis: aws.stream.arn (StreamARN). The value was already read for Data Streams Monitoring but never set on the span. - Step Functions: aws.state_machine.arn and statemachinearn (StateMachineArn) plus aws.execution.arn (ExecutionArn). The plain statemachinearn name matches the tag dd-trace-py and dd-trace-js emit. - Lambda: aws.function.name and functionname (FunctionName) so Invoke spans name their target the same way the Python and JS tracers do. No additional network calls are made; every value is a request field. Existing tags are unchanged and peer.service derivation is untouched. SDK v2 Step Functions members are lowerCamelCase in the service model (getValueForField matches the model member name verbatim), so the Step Functions fields are read as stateMachineArn and executionArn. SDK v1 gains getStateMachineArn, getExecutionArn and getFunctionName method handles on GetterAccess. Tests cover both SDK versions for SNS ARN tags, Step Functions StartExecution and DescribeExecution, and Lambda Invoke, and the Kinesis DSM tests now assert aws.stream.arn. --- .../aws-java/aws-java-sdk-1.11/build.gradle | 9 +++++ .../aws/v0/AwsSdkClientDecorator.java | 19 ++++++++++ .../instrumentation/aws/v0/GetterAccess.java | 18 ++++++++++ .../src/test/groovy/AWS1ClientTest.groovy | 15 +++++--- .../groovy/LegacyAWS1ClientForkedTest.groovy | 2 +- .../aws-java/aws-java-sdk-2.2/build.gradle | 2 ++ .../groovy/Aws2KinesisDataStreamsTest.groovy | 2 ++ .../groovy/Aws2SnsDataStreamsTest.groovy | 4 +++ .../aws/v2/AwsSdkClientDecorator.java | 31 ++++++++++++++++ .../src/test/groovy/Aws2ClientTest.groovy | 35 +++++++++++++++++++ .../groovy/LegacyAws2ClientForkedTest.groovy | 4 +++ .../api/InstrumentationTags.java | 11 ++++++ 12 files changed, 146 insertions(+), 6 deletions(-) diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/build.gradle b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/build.gradle index e8173d6afe0..4db7c0a9799 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/build.gradle +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/build.gradle @@ -49,6 +49,7 @@ dependencies { testImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '[1.11,1.11.106]' testImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: '[1.11,1.11.106]' testImplementation group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '[1.11,1.11.106]' + testImplementation group: 'com.amazonaws', name: 'aws-java-sdk-stepfunctions', version: '[1.11,1.11.106]' // needed for kinesis: testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-cbor', version: null @@ -90,6 +91,13 @@ dependencies { strictly '1.11.0' } } + // aws-java-sdk-stepfunctions (test dependency below 1.11.106) only exists from 1.11.63 and would + // otherwise pull a newer core than the 1.11.0 clients pinned above. + test_before_1_11_106Implementation(group: 'com.amazonaws', name: 'aws-java-sdk-core') { + version { + strictly '1.11.0' + } + } dsmTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-kinesis', version: '1.12.366' // no batch publish before v1.12 @@ -104,6 +112,7 @@ dependencies { latestDepTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '+' latestDepTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: '+' latestDepTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '+' + latestDepTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-stepfunctions', version: '+' } tasks.named("test", Test) { diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java index 07027e0f4f6..4eee500a780 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java @@ -153,6 +153,8 @@ protected void doOnRequest(final AgentSpan span, final Request request) { String topicName = null; String topicArn = access.getTopicArn(originalRequest); if (null != topicArn) { + span.setTag(InstrumentationTags.AWS_TOPIC_ARN, topicArn); + span.setTag(InstrumentationTags.SNS_TOPIC_ARN, topicArn); topicName = topicArn.substring(topicArn.lastIndexOf(':') + 1); span.setTag(InstrumentationTags.AWS_TOPIC_NAME, topicName); span.setTag(InstrumentationTags.TOPIC_NAME, topicName); @@ -168,6 +170,7 @@ protected void doOnRequest(final AgentSpan span, final Request request) { } String streamArn = access.getStreamARN(originalRequest); if (null != streamArn) { + span.setTag(InstrumentationTags.AWS_STREAM_ARN, streamArn); int streamNameStart = streamArn.indexOf(":stream/"); if (streamNameStart >= 0) { streamName = streamArn.substring(streamNameStart + 8); @@ -184,6 +187,22 @@ protected void doOnRequest(final AgentSpan span, final Request request) { bestPrecursor = InstrumentationTags.AWS_TABLE_NAME; bestPeerService = tableName; } + String stateMachineArn = access.getStateMachineArn(originalRequest); + if (null != stateMachineArn) { + span.setTag(InstrumentationTags.AWS_STATE_MACHINE_ARN, stateMachineArn); + span.setTag(InstrumentationTags.STATE_MACHINE_ARN, stateMachineArn); + } + String executionArn = access.getExecutionArn(originalRequest); + if (null != executionArn) { + span.setTag(InstrumentationTags.AWS_EXECUTION_ARN, executionArn); + } + if ("AWSLambda".equalsIgnoreCase(awsServiceName)) { + String functionName = access.getFunctionName(originalRequest); + if (null != functionName) { + span.setTag(InstrumentationTags.AWS_FUNCTION_NAME, functionName); + span.setTag(InstrumentationTags.FUNCTION_NAME, functionName); + } + } // Set peer.service based on Config for serverless functions if (Config.get().isAwsServerless()) { diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/GetterAccess.java b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/GetterAccess.java index ed87408c1bd..8ff4ff87972 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/GetterAccess.java +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/main/java/datadog/trace/instrumentation/aws/v0/GetterAccess.java @@ -39,6 +39,9 @@ static GetterAccess of(final Object request) { private final MethodHandle getPublishBatchRequestEntries; private final MethodHandle getApproximateArrivalTimestamp; private final MethodHandle getTableName; + private final MethodHandle getStateMachineArn; + private final MethodHandle getExecutionArn; + private final MethodHandle getFunctionName; private GetterAccess(final Class objectType) { operationName = @@ -55,6 +58,9 @@ private GetterAccess(final Class objectType) { getApproximateArrivalTimestamp = findGetter(objectType, "getApproximateArrivalTimestamp", Date.class); getTableName = findStringGetter(objectType, "getTableName"); + getStateMachineArn = findStringGetter(objectType, "getStateMachineArn"); + getExecutionArn = findStringGetter(objectType, "getExecutionArn"); + getFunctionName = findStringGetter(objectType, "getFunctionName"); } String getOperationNameFromType() { @@ -101,6 +107,18 @@ String getTableName(final Object object) { return invokeForString(getTableName, object); } + String getStateMachineArn(final Object object) { + return invokeForString(getStateMachineArn, object); + } + + String getExecutionArn(final Object object) { + return invokeForString(getExecutionArn, object); + } + + String getFunctionName(final Object object) { + return invokeForString(getFunctionName, object); + } + Date getApproximateArrivalTimestamp(final Object object) { return invoke(getApproximateArrivalTimestamp, object); } diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/AWS1ClientTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/AWS1ClientTest.groovy index c5ea6ca23cf..3ebf1eeb109 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/AWS1ClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/AWS1ClientTest.groovy @@ -32,6 +32,9 @@ import com.amazonaws.services.rds.model.DeleteOptionGroupRequest import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.services.s3.AmazonS3ClientBuilder import com.amazonaws.services.sns.AmazonSNSClientBuilder +import com.amazonaws.services.stepfunctions.AWSStepFunctionsClientBuilder +import com.amazonaws.services.stepfunctions.model.DescribeExecutionRequest +import com.amazonaws.services.stepfunctions.model.StartExecutionRequest import com.amazonaws.services.sns.model.PublishRequest import com.amazonaws.services.sqs.AmazonSQSClientBuilder import com.amazonaws.services.sqs.model.CreateQueueRequest @@ -176,7 +179,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { "$Tags.PEER_PORT" server.address.port "$Tags.PEER_HOSTNAME" "localhost" "aws.service" { it.contains(service) } - "aws_service" { it.contains(service.toLowerCase()) } + "aws_service" { it.toLowerCase().contains(service.toLowerCase()) } "aws.endpoint" "$server.address" "aws.operation" "${operation}Request" "aws.agent" "java-aws-sdk" @@ -211,6 +214,8 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { "DynamoDBv2" | "CreateTable" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | "aws.table.name" | null "DynamoDBv2" | "GetItem" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getItem(new GetItemRequest("sometable", ["attribute": new AttributeValue("somevalue")])) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | "aws.table.name" | null "Kinesis" | "DeleteStream" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | "aws.stream.name" | null + "AWSStepFunctions" | "StartExecution" | "POST" | "/" | AWSStepFunctionsClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.startExecution(new StartExecutionRequest().withStateMachineArn("arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine")) } | ["aws.state_machine.arn": "arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine", "statemachinearn": "arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine"] | "" | null | null + "AWSStepFunctions" | "DescribeExecution" | "POST" | "/" | AWSStepFunctionsClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.describeExecution(new DescribeExecutionRequest().withExecutionArn("arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution")) } | ["aws.execution.arn": "arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution"] | "" | null | null "SQS" | "CreateQueue" | "POST" | "/" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue", "queuename": "somequeue"] | """ @@ -228,7 +233,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { 27daac76-34dd-47df-bd01-1f6e873584a0 """ | "aws.queue.url" | "/SendMessageResponse/SendMessageResult" - "SNS" | "Publish" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic"] | """ + "SNS" | "Publish" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic", "aws.topic.arn": "arn:aws:sns::123:some-topic", "aws.sns.topic_arn": "arn:aws:sns::123:some-topic"] | """ 567910cd-659e-55d4-8ccb-5aaf14679dc0 @@ -280,7 +285,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { "$Tags.PEER_HOSTNAME" "localhost" "$Tags.PEER_PORT" 61 "aws.service" { it.contains(service) } - "aws_service" { it.contains(service.toLowerCase()) } + "aws_service" { it.toLowerCase().contains(service.toLowerCase()) } "aws.endpoint" "http://localhost:${UNUSABLE_PORT}" "aws.operation" "${operation}Request" "aws.agent" "java-aws-sdk" @@ -453,7 +458,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { "$Tags.PEER_PORT" server.address.port "$Tags.PEER_HOSTNAME" "localhost" "aws.service" { it.contains(service) } - "aws_service" { it.contains(service.toLowerCase()) } + "aws_service" { it.toLowerCase().contains(service.toLowerCase()) } "aws.endpoint" "$server.address" "aws.operation" "${operation}Request" "aws.agent" "java-aws-sdk" @@ -490,7 +495,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { "S3" | "CreateBucket" | "PUT" | "/test-bucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("test-bucket") } | ["aws.bucket.name": "test-bucket", "bucketname": "test-bucket"] | "" | null "SQS" | "CreateQueue" | "POST" | "/" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("test-queue")) } | ["aws.queue.name": "test-queue", "queuename": "test-queue"] | """https://queue.amazonaws.com/123456789012/test-queuetest-request-id""" | "/CreateQueueResponse/CreateQueueResult" "SQS" | "SendMessage" | "POST" | "/test-queue-url" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.sendMessage(new SendMessageRequest("test-queue-url", "test")) } | ["aws.queue.url": "test-queue-url"] | """098f6bcd4621d373cade4e832627b4f6test-msg-idtest-request-id""" | "/SendMessageResponse/SendMessageResult" - "SNS" | "Publish" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:test-topic", "test")) } | ["aws.topic.name": "test-topic", "topicname": "test-topic"] | """test-msg-idtest-request-id""" | "/PublishResponse/PublishResult" + "SNS" | "Publish" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:test-topic", "test")) } | ["aws.topic.name": "test-topic", "topicname": "test-topic", "aws.topic.arn": "arn:aws:sns::123:test-topic", "aws.sns.topic_arn": "arn:aws:sns::123:test-topic"] | """test-msg-idtest-request-id""" | "/PublishResponse/PublishResult" "DynamoDBv2" | "CreateTable" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("test-table", null)) } | ["aws.table.name": "test-table", "tablename": "test-table"] | "" | null "Kinesis" | "DeleteStream" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("test-stream")) } | ["aws.stream.name": "test-stream", "streamname": "test-stream"] | "" | null } diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/LegacyAWS1ClientForkedTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/LegacyAWS1ClientForkedTest.groovy index 2a4a9d8a869..b84efe0f012 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/LegacyAWS1ClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/groovy/LegacyAWS1ClientForkedTest.groovy @@ -227,7 +227,7 @@ class LegacyAWS1ClientForkedTest extends InstrumentationSpecification { 27daac76-34dd-47df-bd01-1f6e873584a0 """ | "/SendMessageResponse/SendMessageResult" - "SNS" | "Publish" | "sns" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic"] | """ + "SNS" | "Publish" | "sns" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic", "aws.topic.arn": "arn:aws:sns::123:some-topic", "aws.sns.topic_arn": "arn:aws:sns::123:some-topic"] | """ 567910cd-659e-55d4-8ccb-5aaf14679dc0 diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/build.gradle b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/build.gradle index 7945dca62b9..ccf09d974a4 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/build.gradle +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/build.gradle @@ -44,6 +44,8 @@ dependencies { testImplementation group: 'software.amazon.awssdk', name: 'sns', version: '2.2.0' testImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '2.2.0' testImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '2.2.0' + testImplementation group: 'software.amazon.awssdk', name: 'sfn', version: '2.2.0' + testImplementation group: 'software.amazon.awssdk', name: 'lambda', version: '2.2.0' testImplementation group: 'org.eclipse.jetty.http2', name: 'http2-server', version: '9.4.56.v20240826' testImplementation libs.testcontainers.localstack diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2KinesisDataStreamsTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2KinesisDataStreamsTest.groovy index 1873d8765eb..b0bfd0ca9d4 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2KinesisDataStreamsTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2KinesisDataStreamsTest.groovy @@ -201,6 +201,7 @@ abstract class Aws2KinesisDataStreamsTest extends VersionedNamingTestBase { "aws.requestId" "$requestId" "aws.stream.name" "somestream" "streamname" "somestream" + "aws.stream.arn" "arnprefix:stream/somestream" "$DDTags.PATHWAY_HASH" { String } @@ -347,6 +348,7 @@ abstract class Aws2KinesisDataStreamsTest extends VersionedNamingTestBase { "aws.requestId" "$requestId" "aws.stream.name" "somestream" "streamname" "somestream" + "aws.stream.arn" "arnprefix:stream/somestream" "$DDTags.PATHWAY_HASH" { String } diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2SnsDataStreamsTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2SnsDataStreamsTest.groovy index d8d69c5ed67..e6973f99632 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2SnsDataStreamsTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/dsmTest/groovy/Aws2SnsDataStreamsTest.groovy @@ -186,6 +186,8 @@ abstract class Aws2SnsDataStreamsTest extends VersionedNamingTestBase { "aws.requestId" "$requestId" "aws.topic.name" "mytopic" "topicname" "mytopic" + "aws.topic.arn" "arnprefix:mytopic" + "aws.sns.topic_arn" "arnprefix:mytopic" "$DDTags.PATHWAY_HASH" { String } @@ -296,6 +298,8 @@ abstract class Aws2SnsDataStreamsTest extends VersionedNamingTestBase { "aws.requestId" "$requestId" "aws.topic.name" "mytopic" "topicname" "mytopic" + "aws.topic.arn" "arnprefix:mytopic" + "aws.sns.topic_arn" "arnprefix:mytopic" "$DDTags.PATHWAY_HASH" { String } diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java index 7c1a1a85b90..d6d4b6c3a01 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java @@ -166,6 +166,11 @@ public void onSdkRequest( if (!snsTopicArn.isPresent()) { snsTopicArn = request.getValueForField("TargetArn", String.class); } + snsTopicArn.ifPresent( + arn -> { + span.setTag(InstrumentationTags.AWS_TOPIC_ARN, arn); + span.setTag(InstrumentationTags.SNS_TOPIC_ARN, arn); + }); Optional snsTopicName = snsTopicArn.map(arn -> arn.substring(arn.lastIndexOf(':') + 1)); snsTopicName.ifPresent(topic -> setTopicName(span, topic)); @@ -176,6 +181,7 @@ public void onSdkRequest( Optional kinesisStreamArn = request.getValueForField("StreamARN", String.class); kinesisStreamArn.ifPresent( streamArn -> { + span.setTag(InstrumentationTags.AWS_STREAM_ARN, streamArn); if (traceConfig().isDataStreamsEnabled()) { attributes.putAttribute(KINESIS_STREAM_ARN_ATTRIBUTE, streamArn); } @@ -188,6 +194,31 @@ public void onSdkRequest( // DynamoDB request.getValueForField("TableName", String.class).ifPresent(name -> setTableName(span, name)); + // Step Functions. getValueForField matches the API model's member name verbatim, and the + // Step Functions model declares its members in lowerCamelCase (unlike SNS "TopicArn" or + // DynamoDB "TableName"), so "StateMachineArn" would never match here. + request + .getValueForField("stateMachineArn", String.class) + .ifPresent( + arn -> { + span.setTag(InstrumentationTags.AWS_STATE_MACHINE_ARN, arn); + span.setTag(InstrumentationTags.STATE_MACHINE_ARN, arn); + }); + request + .getValueForField("executionArn", String.class) + .ifPresent(arn -> span.setTag(InstrumentationTags.AWS_EXECUTION_ARN, arn)); + + // Lambda + if ("lambda".equalsIgnoreCase(awsServiceName)) { + request + .getValueForField("FunctionName", String.class) + .ifPresent( + name -> { + span.setTag(InstrumentationTags.AWS_FUNCTION_NAME, name); + span.setTag(InstrumentationTags.FUNCTION_NAME, name); + }); + } + // DSM if (traceConfig().isDataStreamsEnabled()) { if (kinesisStreamArn.isPresent() diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy index aa7ecf0cd24..acc5053ec24 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy @@ -26,6 +26,9 @@ import software.amazon.awssdk.services.ec2.Ec2AsyncClient import software.amazon.awssdk.services.ec2.Ec2Client import software.amazon.awssdk.services.kinesis.KinesisClient import software.amazon.awssdk.services.kinesis.model.DeleteStreamRequest +import software.amazon.awssdk.services.lambda.LambdaAsyncClient +import software.amazon.awssdk.services.lambda.LambdaClient +import software.amazon.awssdk.services.lambda.model.InvokeRequest import software.amazon.awssdk.services.rds.RdsAsyncClient import software.amazon.awssdk.services.rds.RdsClient import software.amazon.awssdk.services.rds.model.DeleteOptionGroupRequest @@ -35,6 +38,10 @@ import software.amazon.awssdk.services.s3.model.CreateBucketRequest import software.amazon.awssdk.services.s3.model.GetObjectRequest import software.amazon.awssdk.services.s3.model.PutObjectRequest import software.amazon.awssdk.services.s3.model.StorageClass +import software.amazon.awssdk.services.sfn.SfnAsyncClient +import software.amazon.awssdk.services.sfn.SfnClient +import software.amazon.awssdk.services.sfn.model.DescribeExecutionRequest +import software.amazon.awssdk.services.sfn.model.StartExecutionRequest import software.amazon.awssdk.services.sns.SnsAsyncClient import software.amazon.awssdk.services.sns.SnsClient import software.amazon.awssdk.services.sns.model.PublishRequest @@ -167,8 +174,18 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { } else if (service == "Sns" && operation == "Publish") { "aws.topic.name" "some-topic" "topicname" "some-topic" + "aws.topic.arn" "arn:aws:sns::123:some-topic" + "aws.sns.topic_arn" "arn:aws:sns::123:some-topic" peerServiceFrom("aws.topic.name") checkPeerService = true + } else if (service == "Sfn" && operation == "StartExecution") { + "aws.state_machine.arn" "arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine" + "statemachinearn" "arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine" + } else if (service == "Sfn" && operation == "DescribeExecution") { + "aws.execution.arn" "arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution" + } else if (service == "Lambda") { + "aws.function.name" "somefunction" + "functionname" "somefunction" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" @@ -203,6 +220,9 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { "DynamoDb" | "GetItem" | "POST" | "/" | "UNKNOWN" | DynamoDbClient.builder() | { c -> c.getItem(GetItemRequest.builder().tableName("sometable").key(["attribute": AttributeValue.builder().s("somevalue").build()]).build()) } | "" "DynamoDb" | "UpdateItem" | "POST" | "/" | "UNKNOWN" | DynamoDbClient.builder() | { c -> c.updateItem(UpdateItemRequest.builder().tableName("sometable").key(["attribute": AttributeValue.builder().s("somevalue").build()]).build()) } | "" "Kinesis" | "DeleteStream" | "POST" | "/" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sfn" | "StartExecution" | "POST" | "/" | "UNKNOWN" | SfnClient.builder() | { c -> c.startExecution(StartExecutionRequest.builder().stateMachineArn("arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine").build()) } | """{"executionArn":"arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution","startDate":1.0E9}""" + "Sfn" | "DescribeExecution" | "POST" | "/" | "UNKNOWN" | SfnClient.builder() | { c -> c.describeExecution(DescribeExecutionRequest.builder().executionArn("arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution").build()) } | """{"executionArn":"arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution","stateMachineArn":"arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine","name":"someexecution","status":"SUCCEEDED","startDate":1.0E9}""" + "Lambda" | "Invoke" | "POST" | "/2015-03-31/functions/somefunction/invocations" | "UNKNOWN" | LambdaClient.builder() | { c -> c.invoke(InvokeRequest.builder().functionName("somefunction").build()) } | "{}" "Sqs" | "CreateQueue" | "POST" | "/" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue @@ -334,8 +354,18 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { } else if (service == "Sns" && operation == "Publish") { "aws.topic.name" "some-topic" "topicname" "some-topic" + "aws.topic.arn" "arn:aws:sns::123:some-topic" + "aws.sns.topic_arn" "arn:aws:sns::123:some-topic" peerServiceFrom("aws.topic.name") checkPeerService = true + } else if (service == "Sfn" && operation == "StartExecution") { + "aws.state_machine.arn" "arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine" + "statemachinearn" "arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine" + } else if (service == "Sfn" && operation == "DescribeExecution") { + "aws.execution.arn" "arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution" + } else if (service == "Lambda") { + "aws.function.name" "somefunction" + "functionname" "somefunction" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" @@ -370,6 +400,9 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { "DynamoDb" | "UpdateItem" | "POST" | "/" | "UNKNOWN" | DynamoDbAsyncClient.builder() | { c -> c.updateItem(UpdateItemRequest.builder().tableName("sometable").key(["attribute": AttributeValue.builder().s("somevalue").build()]).build()) } | "" // Kinesis seems to expect an http2 response which is incompatible with our test server. // "Kinesis" | "DeleteStream" | "java-aws-sdk" | "POST" | "/" | "UNKNOWN" | KinesisAsyncClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sfn" | "StartExecution" | "POST" | "/" | "UNKNOWN" | SfnAsyncClient.builder() | { c -> c.startExecution(StartExecutionRequest.builder().stateMachineArn("arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine").build()) } | """{"executionArn":"arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution","startDate":1.0E9}""" + "Sfn" | "DescribeExecution" | "POST" | "/" | "UNKNOWN" | SfnAsyncClient.builder() | { c -> c.describeExecution(DescribeExecutionRequest.builder().executionArn("arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution").build()) } | """{"executionArn":"arn:aws:states:us-east-1:123456789012:execution:somestatemachine:someexecution","stateMachineArn":"arn:aws:states:us-east-1:123456789012:stateMachine:somestatemachine","name":"someexecution","status":"SUCCEEDED","startDate":1.0E9}""" + "Lambda" | "Invoke" | "POST" | "/2015-03-31/functions/somefunction/invocations" | "UNKNOWN" | LambdaAsyncClient.builder() | { c -> c.invoke(InvokeRequest.builder().functionName("somefunction").build()) } | "{}" "Sqs" | "CreateQueue" | "POST" | "/" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue @@ -576,6 +609,8 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { } else if (service == "Sns" && operation == "Publish") { "aws.topic.name" "test-topic" "topicname" "test-topic" + "aws.topic.arn" "arn:aws:sns::123:test-topic" + "aws.sns.topic_arn" "arn:aws:sns::123:test-topic" } else if (service == "DynamoDb") { "aws.table.name" "test-table" "tablename" "test-table" diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/LegacyAws2ClientForkedTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/LegacyAws2ClientForkedTest.groovy index d86991f0030..fc57b7b3214 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/LegacyAws2ClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/groovy/LegacyAws2ClientForkedTest.groovy @@ -144,6 +144,8 @@ class LegacyAws2ClientForkedTest extends InstrumentationSpecification { } else if (service == "Sns" && operation == "Publish") { "aws.topic.name" "some-topic" "topicname" "some-topic" + "aws.topic.arn" "arn:aws:sns::123:some-topic" + "aws.sns.topic_arn" "arn:aws:sns::123:some-topic" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" @@ -284,6 +286,8 @@ class LegacyAws2ClientForkedTest extends InstrumentationSpecification { } else if (service == "Sns" && operation == "Publish") { "aws.topic.name" "some-topic" "topicname" "some-topic" + "aws.topic.arn" "arn:aws:sns::123:some-topic" + "aws.sns.topic_arn" "arn:aws:sns::123:some-topic" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InstrumentationTags.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InstrumentationTags.java index 0c1054e7776..a33e67d7c64 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InstrumentationTags.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InstrumentationTags.java @@ -37,6 +37,17 @@ public class InstrumentationTags { public static final String AWS_REQUEST_ID = "aws.requestId"; public static final String AWS_STORAGE_CLASS = "aws.storage.class"; + // Complete resource identity carried by the request. The plain-name variants match the + // tags emitted by dd-trace-py and dd-trace-js so the same query works across tracers. + public static final String AWS_TOPIC_ARN = "aws.topic.arn"; + public static final String SNS_TOPIC_ARN = "aws.sns.topic_arn"; + public static final String AWS_STREAM_ARN = "aws.stream.arn"; + public static final String AWS_STATE_MACHINE_ARN = "aws.state_machine.arn"; + public static final String STATE_MACHINE_ARN = "statemachinearn"; + public static final String AWS_EXECUTION_ARN = "aws.execution.arn"; + public static final String AWS_FUNCTION_NAME = "aws.function.name"; + public static final String FUNCTION_NAME = "functionname"; + // These are temporary keys used for span pointer hash calculation public static final String S3_ETAG = "s3.eTag"; public static final String DYNAMO_PRIMARY_KEY_1 = "dynamodb.primary_key_1";