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/dsmTest/groovy/AWS1KinesisClientTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1KinesisClientTest.groovy index 5b6c8d782e7..45d499f28b2 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1KinesisClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1KinesisClientTest.groovy @@ -149,6 +149,7 @@ abstract class AWS1KinesisClientTest extends VersionedNamingTestBase { "aws.agent" "java-aws-sdk" "aws.stream.name" streamName "streamname" streamName + "aws.stream.arn" streamArn "$DDTags.PATHWAY_HASH" { String } peerServiceFrom("aws.stream.name") defaultTags() diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1SnsClientTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1SnsClientTest.groovy index ca4ced2dbe0..ee12ec0c1e7 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1SnsClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/dsmTest/groovy/AWS1SnsClientTest.groovy @@ -130,6 +130,7 @@ abstract class AWS1SnsClientTest extends VersionedNamingTestBase { "aws.agent" "java-aws-sdk" "aws.topic.name" topicName "topicname" topicName + "aws.topic.arn" topicArn "$DDTags.PATHWAY_HASH" { String } peerServiceFrom("aws.topic.name") defaultTags() 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 6d3c40adc04..38ed17a38d0 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 @@ -154,6 +154,7 @@ 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); topicName = topicArn.substring(topicArn.lastIndexOf(':') + 1); span.setTag(InstrumentationTags.AWS_TOPIC_NAME, topicName); span.setTag(InstrumentationTags.TOPIC_NAME, topicName); @@ -169,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); @@ -185,6 +187,20 @@ 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); + } + 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) { + setFunctionIdentity(span, functionName); + } + } // Set peer.service based on Config for serverless functions if (Config.get().isAwsServerless()) { @@ -248,6 +264,44 @@ protected void doOnRequest(final AgentSpan span, final Request request) { } } + /** + * Normalizes a Lambda {@code FunctionName} request value. {@code aws.function.name} is always the + * bare function name (account, {@code function:} prefix and {@code :qualifier} suffix stripped), + * and {@code aws.function.arn} is set only when the value is a full ARN. A partial ARN ({@code + * account:function:name}) yields the name but no ARN, because its Region and partition are + * unknown and must not be invented. + */ + private static void setFunctionIdentity(AgentSpan span, String functionName) { + if (functionName == null || functionName.isEmpty()) { + return; + } + if (functionName.startsWith("arn:")) { + // arn::lambda:::function:[:] + span.setTag(InstrumentationTags.AWS_FUNCTION_ARN, functionName); + } + span.setTag(InstrumentationTags.AWS_FUNCTION_NAME, lambdaFunctionName(functionName)); + } + + /** Extracts the bare function name from any accepted {@code FunctionName} form. */ + static String lambdaFunctionName(String functionName) { + String[] parts = functionName.split(":"); + // A colon-only value (":", "::") splits to an empty array; leave such malformed input to the + // SDK/service to reject rather than throwing from the tracing callback. + if (parts.length == 0) { + return functionName; + } + // Full ARN: arn::lambda:::function:[:] + if (parts.length >= 7 && "arn".equals(parts[0]) && "function".equals(parts[5])) { + return parts[6]; + } + // Partial ARN: :function:[:] + if (parts.length >= 3 && "function".equals(parts[1])) { + return parts[2]; + } + // Bare name, optionally with a :alias or :version qualifier (parts[0] is the name). + return parts[0]; + } + public void onServiceResponse( final AgentSpan span, final String awsService, final Response response) { if ("s3".equalsIgnoreCase(simplifyServiceName(awsService)) 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..76b285c113c 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"] | "" | 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"] | """ 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"] | """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..74168681d3d 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"] | """ 567910cd-659e-55d4-8ccb-5aaf14679dc0 diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/java/datadog/trace/instrumentation/aws/v0/LambdaFunctionNameTest.java b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/java/datadog/trace/instrumentation/aws/v0/LambdaFunctionNameTest.java new file mode 100644 index 00000000000..0b711941963 --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-1.11/src/test/java/datadog/trace/instrumentation/aws/v0/LambdaFunctionNameTest.java @@ -0,0 +1,41 @@ +package datadog.trace.instrumentation.aws.v0; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Verifies that every accepted AWS Lambda {@code FunctionName} form normalizes to the bare function + * name for {@code aws.function.name}, so the same logical function is not split across tag values. + */ +class LambdaFunctionNameTest { + + @ParameterizedTest + @CsvSource({ + // bare name + "my-function, my-function", + // name with an alias qualifier + "my-function:PROD, my-function", + // name with a numeric version qualifier + "my-function:42, my-function", + // partial ARN (account:function:name) + "123456789012:function:my-function, my-function", + // full ARN + "arn:aws:lambda:us-east-1:123456789012:function:my-function, my-function", + // full ARN with an alias qualifier + "arn:aws:lambda:us-east-1:123456789012:function:my-function:PROD, my-function", + // full ARN with a numeric version qualifier + "arn:aws:lambda:us-east-1:123456789012:function:my-function:42, my-function", + // GovCloud partition full ARN + "arn:aws-us-gov:lambda:us-gov-west-1:123456789012:function:my-function, my-function", + // bare name that literally contains 'function', with a qualifier (regression) + "my-function-handler:PROD, my-function-handler", + // colon-only values split to an empty array; must not throw, returned unchanged (regression) + "':', ':'", + "'::', '::'", + }) + void normalizesFunctionNameToBareName(String functionName, String expectedName) { + assertEquals(expectedName, AwsSdkClientDecorator.lambdaFunctionName(functionName)); + } +} 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 cfdc266d8ec..0fe9a780a9a 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 @@ -50,6 +50,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 @@ -91,6 +93,8 @@ dependencies { latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sns', version: fixedSdkVersion latestDepTestImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: fixedSdkVersion latestDepTestImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: fixedSdkVersion + latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sfn', version: fixedSdkVersion + latestDepTestImplementation group: 'software.amazon.awssdk', name: 'lambda', version: fixedSdkVersion } tasks.withType(Test).configureEach { 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..7e1d43d0fa1 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,7 @@ abstract class Aws2SnsDataStreamsTest extends VersionedNamingTestBase { "aws.requestId" "$requestId" "aws.topic.name" "mytopic" "topicname" "mytopic" + "aws.topic.arn" "arnprefix:mytopic" "$DDTags.PATHWAY_HASH" { String } @@ -296,6 +297,7 @@ abstract class Aws2SnsDataStreamsTest extends VersionedNamingTestBase { "aws.requestId" "$requestId" "aws.topic.name" "mytopic" "topicname" "mytopic" + "aws.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 33e1f1b8315..5bb39d69351 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 @@ -162,12 +162,13 @@ public void onSdkRequest( }); request.getValueForField("QueueName", String.class).ifPresent(name -> setQueueName(span, name)); - // SNS + // SNS. TargetArn may identify a mobile platform endpoint, so only TopicArn is a topic ARN. Optional snsTopicArn = request.getValueForField("TopicArn", String.class); - if (!snsTopicArn.isPresent()) { - snsTopicArn = request.getValueForField("TargetArn", String.class); - } - Optional snsTopicName = snsTopicArn.map(arn -> arn.substring(arn.lastIndexOf(':') + 1)); + snsTopicArn.ifPresent(arn -> span.setTag(InstrumentationTags.AWS_TOPIC_ARN, arn)); + Optional snsTargetArn = + snsTopicArn.isPresent() ? snsTopicArn : request.getValueForField("TargetArn", String.class); + Optional snsTopicName = + snsTargetArn.map(arn -> arn.substring(arn.lastIndexOf(':') + 1)); snsTopicName.ifPresent(topic -> setTopicName(span, topic)); // Kinesis @@ -177,6 +178,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); } @@ -189,6 +191,27 @@ 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)); + request + .getValueForField("executionArn", String.class) + .ifPresent(arn -> span.setTag(InstrumentationTags.AWS_EXECUTION_ARN, arn)); + + // Lambda. FunctionName accepts a bare name, name:qualifier, partial ARN + // (account:function:name) or full ARN, and cross-account/cross-Region invokes require a full + // ARN, so pass-through would fragment aws.function.name across forms. Normalize the name and + // only emit aws.function.arn when the request carries a full ARN. A separate Qualifier field + // never changes the resource name. + if ("lambda".equalsIgnoreCase(awsServiceName)) { + request + .getValueForField("FunctionName", String.class) + .ifPresent(name -> setFunctionIdentity(span, name)); + } + // DSM if (traceConfig().isDataStreamsEnabled()) { if (kinesisStreamArn.isPresent() @@ -309,6 +332,44 @@ private static void setTableName(AgentSpan span, String name) { setPeerService(span, InstrumentationTags.AWS_TABLE_NAME, name); } + /** + * Normalizes a Lambda {@code FunctionName} request value. {@code aws.function.name} is always the + * bare function name (account, {@code function:} prefix and {@code :qualifier} suffix stripped), + * and {@code aws.function.arn} is set only when the value is a full ARN. A partial ARN ({@code + * account:function:name}) yields the name but no ARN, because its Region and partition are + * unknown and must not be invented. + */ + private static void setFunctionIdentity(AgentSpan span, String functionName) { + if (functionName == null || functionName.isEmpty()) { + return; + } + if (functionName.startsWith("arn:")) { + // arn::lambda:::function:[:] + span.setTag(InstrumentationTags.AWS_FUNCTION_ARN, functionName); + } + span.setTag(InstrumentationTags.AWS_FUNCTION_NAME, lambdaFunctionName(functionName)); + } + + /** Extracts the bare function name from any accepted {@code FunctionName} form. */ + static String lambdaFunctionName(String functionName) { + String[] parts = functionName.split(":"); + // A colon-only value (":", "::") splits to an empty array; leave such malformed input to the + // SDK/service to reject rather than throwing from the tracing callback. + if (parts.length == 0) { + return functionName; + } + // Full ARN: arn::lambda:::function:[:] + if (parts.length >= 7 && "arn".equals(parts[0]) && "function".equals(parts[5])) { + return parts[6]; + } + // Partial ARN: :function:[:] + if (parts.length >= 3 && "function".equals(parts[1])) { + return parts[2]; + } + // Bare name, optionally with a :alias or :version qualifier (parts[0] is the name). + return parts[0]; + } + public void onSdkResponse( final Context context, final SdkResponse response, 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..71bce3f1ff1 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,15 @@ 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" 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" + } 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" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" @@ -203,6 +217,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 @@ -266,6 +283,39 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { """ } + def "SNS TargetArn is not tagged as a topic ARN"() { + setup: + def client = SnsClient.builder() + .endpointOverride(server.address) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build() + responseBody.set(""" + + + 567910cd-659e-55d4-8ccb-5aaf14679dc0 + + d74b8436-ae13-5ab4-a9ff-ce54dfea72a0 + + """) + + when: + client.publish( + PublishRequest.builder() + .targetArn("arn:aws:sns:us-east-1:123456789012:endpoint/APNS/app/device") + .message("") + .build()) + TEST_WRITER.waitForTraces(1) + def span = TEST_WRITER.flatten().first() + + then: + span.getTag("aws.topic.arn") == null + span.getTag("aws.topic.name") == "endpoint/APNS/app/device" + + cleanup: + client.close() + } + def "send #operation async request with builder {#builder.class.getName()} mocked response"() { setup: boolean executed = false @@ -334,8 +384,15 @@ 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" 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" + } 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" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" @@ -370,6 +427,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 +636,7 @@ 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" } 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..ef906f2fc64 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,7 @@ 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" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" @@ -284,6 +285,7 @@ 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" } else if (service == "DynamoDb") { "aws.table.name" "sometable" "tablename" "sometable" diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/java/datadog/trace/instrumentation/aws/v2/LambdaFunctionNameTest.java b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/java/datadog/trace/instrumentation/aws/v2/LambdaFunctionNameTest.java new file mode 100644 index 00000000000..d31574f65a8 --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sdk-2.2/src/test/java/datadog/trace/instrumentation/aws/v2/LambdaFunctionNameTest.java @@ -0,0 +1,41 @@ +package datadog.trace.instrumentation.aws.v2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Verifies that every accepted AWS Lambda {@code FunctionName} form normalizes to the bare function + * name for {@code aws.function.name}, so the same logical function is not split across tag values. + */ +class LambdaFunctionNameTest { + + @ParameterizedTest + @CsvSource({ + // bare name + "my-function, my-function", + // name with an alias qualifier + "my-function:PROD, my-function", + // name with a numeric version qualifier + "my-function:42, my-function", + // partial ARN (account:function:name) + "123456789012:function:my-function, my-function", + // full ARN + "arn:aws:lambda:us-east-1:123456789012:function:my-function, my-function", + // full ARN with an alias qualifier + "arn:aws:lambda:us-east-1:123456789012:function:my-function:PROD, my-function", + // full ARN with a numeric version qualifier + "arn:aws:lambda:us-east-1:123456789012:function:my-function:42, my-function", + // GovCloud partition full ARN + "arn:aws-us-gov:lambda:us-gov-west-1:123456789012:function:my-function, my-function", + // bare name that literally contains 'function', with a qualifier (regression) + "my-function-handler:PROD, my-function-handler", + // colon-only values split to an empty array; must not throw, returned unchanged (regression) + "':', ':'", + "'::', '::'", + }) + void normalizesFunctionNameToBareName(String functionName, String expectedName) { + assertEquals(expectedName, AwsSdkClientDecorator.lambdaFunctionName(functionName)); + } +} diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sns-1.0/src/test/groovy/SnsClientTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sns-1.0/src/test/groovy/SnsClientTest.groovy index a4154bc543d..e98faad08dd 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sns-1.0/src/test/groovy/SnsClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sns-1.0/src/test/groovy/SnsClientTest.groovy @@ -157,6 +157,7 @@ abstract class SnsClientTest extends VersionedNamingTestBase { "aws.agent" "java-aws-sdk" "aws.topic.name" "testtopic" "topicname" "testtopic" + "aws.topic.arn" testTopicARN if ({ isDataStreamsEnabled() }) { "$DDTags.PATHWAY_HASH" { String } } diff --git a/dd-java-agent/instrumentation/aws-java/aws-java-sns-2.0/src/test/groovy/SnsClientTest.groovy b/dd-java-agent/instrumentation/aws-java/aws-java-sns-2.0/src/test/groovy/SnsClientTest.groovy index e6c6930f681..a50534e1a24 100644 --- a/dd-java-agent/instrumentation/aws-java/aws-java-sns-2.0/src/test/groovy/SnsClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java/aws-java-sns-2.0/src/test/groovy/SnsClientTest.groovy @@ -123,6 +123,7 @@ abstract class SnsClientTest extends VersionedNamingTestBase { "aws.agent" "java-aws-sdk" "aws.topic.name" "testtopic" "topicname" "testtopic" + "aws.topic.arn" testTopicARN "aws.requestId" response.responseMetadata().requestId() if ({ isDataStreamsEnabled() }) { "$DDTags.PATHWAY_HASH" { String } 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..6edb66d1f98 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,14 @@ 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. + public static final String AWS_TOPIC_ARN = "aws.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 AWS_EXECUTION_ARN = "aws.execution.arn"; + public static final String AWS_FUNCTION_NAME = "aws.function.name"; + public static final String AWS_FUNCTION_ARN = "aws.function.arn"; + // 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";