diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java index b164cf54349..b80f55a8420 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java @@ -83,12 +83,13 @@ public AgentSpan onRequest(final AgentSpan span, final Request request) { final AmazonWebServiceRequest originalRequest = request.getOriginalRequest(); final Class awsOperation = originalRequest.getClass(); final GetterAccess access = GetterAccess.of(originalRequest); + final String endpoint = request.getEndpoint().toString(); span.setTag(InstrumentationTags.AWS_AGENT, COMPONENT_NAME); span.setTag(InstrumentationTags.AWS_SERVICE, awsServiceName); span.setTag(InstrumentationTags.TOP_LEVEL_AWS_SERVICE, awsSimplifiedServiceName); span.setTag(InstrumentationTags.AWS_OPERATION, awsOperation.getSimpleName()); - span.setTag(InstrumentationTags.AWS_ENDPOINT, request.getEndpoint().toString()); + span.setTag(InstrumentationTags.AWS_ENDPOINT, endpoint); CharSequence awsRequestName = AwsNameCache.getQualifiedName(request); span.setResourceName(awsRequestName, RPC_COMMAND_NAME); @@ -182,11 +183,20 @@ public AgentSpan onRequest(final AgentSpan span, final Request request) { bestPeerService = tableName; } - // for aws we can calculate this eagerly without needing to have to looking up tags in the peer - // service interceptor - if (bestPrecursor != null && SpanNaming.instance().namingSchema().peerService().supports()) { - span.setTag(Tags.PEER_SERVICE, bestPeerService); - span.setTag(DDTags.PEER_SERVICE_SOURCE, bestPrecursor); + // Set peer.service based on Config for serverless functions + if (Config.get().isAwsServerless()) { + URI uri = request.getEndpoint(); + String hostname = uri.getHost(); + if (uri.getPort() != -1) { + hostname = hostname + ":" + uri.getPort(); + } + span.setTag(Tags.PEER_SERVICE, hostname); + span.setTag(DDTags.PEER_SERVICE_SOURCE, "peer.service"); + } else { + if (bestPrecursor != null && SpanNaming.instance().namingSchema().peerService().supports()) { + span.setTag(Tags.PEER_SERVICE, bestPeerService); + span.setTag(DDTags.PEER_SERVICE_SOURCE, bestPrecursor); + } } // DSM diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy index 994caa353f5..dabe083af69 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy @@ -401,6 +401,88 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { cleanup: server.close() } + + def "#service #operation sets peer.service in serverless environment"() { + setup: + + if (version() == 0) { + return + } + + // Set the AWS Lambda function name environment variable + injectEnvConfig("AWS_LAMBDA_FUNCTION_NAME", "my-test-lambda-function", false) + + // Set response body + responseBody.set(body) + if (jsonPointerStr != null) { + jsonPointer.set(jsonPointerStr) + } + + when: + // Make the request + def response = call.call(client) + + // Wait for traces to be written + TEST_WRITER.waitForTraces(1) + + then: + response != null + + // Verify the trace + assertTraces(1) { + trace(1) { + span { + serviceName expectedService(service, operation) + operationName expectedOperation(service, operation) + resourceName "$service.$operation" + spanType DDSpanTypes.HTTP_CLIENT + errored false + measured true + parent() + tags { + "$Tags.COMPONENT" "java-aws-sdk" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT + "$Tags.HTTP_URL" "$server.address/" + "$Tags.HTTP_METHOD" method + "$Tags.HTTP_STATUS" 200 + "$Tags.PEER_PORT" server.address.port + "$Tags.PEER_HOSTNAME" "localhost" + "aws.service" { it.contains(service) } + "aws_service" { it.contains(service.toLowerCase()) } + "aws.endpoint" "$server.address" + "aws.operation" "${operation}Request" + "aws.agent" "java-aws-sdk" + + // Service-specific tags + for (def addedTag : additionalTags) { + "$addedTag.key" "$addedTag.value" + } + + // Test specific peer service assertions in serverless + "peer.service" "${server.address.host}:${server.address.port}" + "_dd.peer.service.source" "peer.service" + + defaultTags(false, true) + } + } + } + } + + cleanup: + + if (jsonPointerStr != null) { + jsonPointer.set(null) + } + + where: + service | operation | method | path | client | call | additionalTags | body | jsonPointerStr + "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" + "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 + } } class AWS1ClientV0Test extends AWS1ClientTest { diff --git a/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java b/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java index c5aa03bf64f..4a270b63772 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java +++ b/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java @@ -208,6 +208,18 @@ public AgentSpan onSdkRequest( } } + // Set peer.service based on Config for serverless functions + if (Config.get().isAwsServerless()) { + URI uri = httpRequest.getUri(); + String hostname = uri.getHost(); + if (uri.getPort() != -1) { + hostname = hostname + ":" + uri.getPort(); + } + + span.setTag(Tags.PEER_SERVICE, hostname); + span.setTag(DDTags.PEER_SERVICE_SOURCE, "peer.service"); + } + return span; } diff --git a/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy index e075fb61892..f8d57ed1542 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy @@ -421,6 +421,112 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase { cleanup: server.close() } + + def "#service #operation sets peer.service in serverless environment"() { + setup: + + if (version() == 0) { + return + } + + // Set the AWS Lambda function name environment variable + injectEnvConfig("AWS_LAMBDA_FUNCTION_NAME", "my-test-lambda-function", false) + + // Create client with mocked endpoint + def client = builder + .endpointOverride(server.address) + .region(Region.US_EAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build() + + // Set response body + responseBody.set(body) + + when: + // Make the request + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + // Wait for traces to be written + TEST_WRITER.waitForTraces(1) + + then: + response != null + + // Verify the trace + assertTraces(1) { + trace(1) { + span { + serviceName expectedService(service, operation) + operationName expectedOperation(service, operation) + resourceName "$service.$operation" + spanType DDSpanTypes.HTTP_CLIENT + errored false + measured true + parent() + tags { + defaultTags(false, true) + + // AWS specific tags + "aws.service" service + "aws_service" service + "aws.operation" operation + "aws.agent" "java-aws-sdk" + "aws.requestId" requestId + + // HTTP tags + "$Tags.HTTP_METHOD" method + "$Tags.HTTP_STATUS" 200 + "$Tags.HTTP_URL" String + + // Peer tags + "$Tags.PEER_HOSTNAME" "localhost" + "$Tags.PEER_PORT" server.address.port + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT + "$Tags.COMPONENT" "java-aws-sdk" + + // Service-specific tags + if (service == "S3") { + "aws.bucket.name" "test-bucket" + "bucketname" "test-bucket" + } else if (service == "Sqs" && operation == "CreateQueue") { + "aws.queue.name" "test-queue" + "queuename" "test-queue" + } else if (service == "Sqs" && operation == "SendMessage") { + "aws.queue.url" "test-queue-url" + } else if (service == "Sns" && operation == "Publish") { + "aws.topic.name" "test-topic" + "topicname" "test-topic" + } else if (service == "DynamoDb") { + "aws.table.name" "test-table" + "tablename" "test-table" + } else if (service == "Kinesis") { + "aws.stream.name" "test-stream" + "streamname" "test-stream" + } + + urlTags("${server.address}${path}", ExpectedQueryParams.getExpectedQueryParams(operation)) + + // Test specific peer service assertions in serverless + "peer.service" "${server.address.host}:${server.address.port}" + "_dd.peer.service.source" "peer.service" + } + } + } + } + + where: + service | operation | method | path | builder | call | body | requestId + "S3" | "CreateBucket" | "PUT" | "/test-bucket" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("test-bucket").build()) } | "" | "UNKNOWN" + "Sqs" | "CreateQueue" | "POST" | "/" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("test-queue").build()) } | """https://queue.amazonaws.com/123456789012/test-queuetest-request-id""" | "test-request-id" + "Sqs" | "SendMessage" | "POST" | "/" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("test-queue-url").messageBody("test").build()) } | """098f6bcd4621d373cade4e832627b4f6test-msg-idtest-request-id""" | "test-request-id" + "Sns" | "Publish" | "POST" | "/" | SnsClient.builder() | { c -> c.publish(PublishRequest.builder().topicArn("arn:aws:sns::123:test-topic").message("test").build()) } | """test-msg-idtest-request-id""" | "test-request-id" + "DynamoDb" | "CreateTable" | "POST" | "/" | DynamoDbClient.builder() | { c -> c.createTable(CreateTableRequest.builder().tableName("test-table").build()) } | "" | "UNKNOWN" + "Kinesis" | "DeleteStream" | "POST" | "/" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("test-stream").build()) } | "" | "UNKNOWN" + } } class Aws2ClientV0ForkedTest extends Aws2ClientTest { diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 0a1727a1b37..5ff002d4144 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -1168,6 +1168,7 @@ public static String getHostName() { private final boolean azureAppServices; private final boolean azureFunctions; + private final boolean awsServerless; private final String traceAgentPath; private final List traceAgentArgs; private final String dogStatsDPath; @@ -1487,6 +1488,9 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins azureFunctions = getEnv("FUNCTIONS_WORKER_RUNTIME") != null && getEnv("FUNCTIONS_EXTENSION_VERSION") != null; + awsServerless = + getEnv("AWS_LAMBDA_FUNCTION_NAME") != null && !getEnv("AWS_LAMBDA_FUNCTION_NAME").isEmpty(); + spanAttributeSchemaVersion = schemaVersionFromConfig(); peerHostNameEnabled = configProvider.getBoolean(TRACE_PEER_HOSTNAME_ENABLED, true); @@ -4270,6 +4274,10 @@ public boolean isAzureAppServices() { return azureAppServices; } + public boolean isAwsServerless() { + return awsServerless; + } + public boolean isDataStreamsEnabled() { return dataStreamsEnabled; }