Skip to content

Commit 4c62061

Browse files
authored
Enhance Service Representation for Serverless (#9203)
* add peer.service in serverless scenarios
1 parent 893941d commit 4c62061

File tree

5 files changed

+224
-6
lines changed

5 files changed

+224
-6
lines changed

dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AwsSdkClientDecorator.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,13 @@ public AgentSpan onRequest(final AgentSpan span, final Request request) {
8383
final AmazonWebServiceRequest originalRequest = request.getOriginalRequest();
8484
final Class<?> awsOperation = originalRequest.getClass();
8585
final GetterAccess access = GetterAccess.of(originalRequest);
86+
final String endpoint = request.getEndpoint().toString();
8687

8788
span.setTag(InstrumentationTags.AWS_AGENT, COMPONENT_NAME);
8889
span.setTag(InstrumentationTags.AWS_SERVICE, awsServiceName);
8990
span.setTag(InstrumentationTags.TOP_LEVEL_AWS_SERVICE, awsSimplifiedServiceName);
9091
span.setTag(InstrumentationTags.AWS_OPERATION, awsOperation.getSimpleName());
91-
span.setTag(InstrumentationTags.AWS_ENDPOINT, request.getEndpoint().toString());
92+
span.setTag(InstrumentationTags.AWS_ENDPOINT, endpoint);
9293

9394
CharSequence awsRequestName = AwsNameCache.getQualifiedName(request);
9495
span.setResourceName(awsRequestName, RPC_COMMAND_NAME);
@@ -182,11 +183,20 @@ public AgentSpan onRequest(final AgentSpan span, final Request request) {
182183
bestPeerService = tableName;
183184
}
184185

185-
// for aws we can calculate this eagerly without needing to have to looking up tags in the peer
186-
// service interceptor
187-
if (bestPrecursor != null && SpanNaming.instance().namingSchema().peerService().supports()) {
188-
span.setTag(Tags.PEER_SERVICE, bestPeerService);
189-
span.setTag(DDTags.PEER_SERVICE_SOURCE, bestPrecursor);
186+
// Set peer.service based on Config for serverless functions
187+
if (Config.get().isAwsServerless()) {
188+
URI uri = request.getEndpoint();
189+
String hostname = uri.getHost();
190+
if (uri.getPort() != -1) {
191+
hostname = hostname + ":" + uri.getPort();
192+
}
193+
span.setTag(Tags.PEER_SERVICE, hostname);
194+
span.setTag(DDTags.PEER_SERVICE_SOURCE, "peer.service");
195+
} else {
196+
if (bestPrecursor != null && SpanNaming.instance().namingSchema().peerService().supports()) {
197+
span.setTag(Tags.PEER_SERVICE, bestPeerService);
198+
span.setTag(DDTags.PEER_SERVICE_SOURCE, bestPrecursor);
199+
}
190200
}
191201

192202
// DSM

dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,88 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase {
401401
cleanup:
402402
server.close()
403403
}
404+
405+
def "#service #operation sets peer.service in serverless environment"() {
406+
setup:
407+
408+
if (version() == 0) {
409+
return
410+
}
411+
412+
// Set the AWS Lambda function name environment variable
413+
injectEnvConfig("AWS_LAMBDA_FUNCTION_NAME", "my-test-lambda-function", false)
414+
415+
// Set response body
416+
responseBody.set(body)
417+
if (jsonPointerStr != null) {
418+
jsonPointer.set(jsonPointerStr)
419+
}
420+
421+
when:
422+
// Make the request
423+
def response = call.call(client)
424+
425+
// Wait for traces to be written
426+
TEST_WRITER.waitForTraces(1)
427+
428+
then:
429+
response != null
430+
431+
// Verify the trace
432+
assertTraces(1) {
433+
trace(1) {
434+
span {
435+
serviceName expectedService(service, operation)
436+
operationName expectedOperation(service, operation)
437+
resourceName "$service.$operation"
438+
spanType DDSpanTypes.HTTP_CLIENT
439+
errored false
440+
measured true
441+
parent()
442+
tags {
443+
"$Tags.COMPONENT" "java-aws-sdk"
444+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
445+
"$Tags.HTTP_URL" "$server.address/"
446+
"$Tags.HTTP_METHOD" method
447+
"$Tags.HTTP_STATUS" 200
448+
"$Tags.PEER_PORT" server.address.port
449+
"$Tags.PEER_HOSTNAME" "localhost"
450+
"aws.service" { it.contains(service) }
451+
"aws_service" { it.contains(service.toLowerCase()) }
452+
"aws.endpoint" "$server.address"
453+
"aws.operation" "${operation}Request"
454+
"aws.agent" "java-aws-sdk"
455+
456+
// Service-specific tags
457+
for (def addedTag : additionalTags) {
458+
"$addedTag.key" "$addedTag.value"
459+
}
460+
461+
// Test specific peer service assertions in serverless
462+
"peer.service" "${server.address.host}:${server.address.port}"
463+
"_dd.peer.service.source" "peer.service"
464+
465+
defaultTags(false, true)
466+
}
467+
}
468+
}
469+
}
470+
471+
cleanup:
472+
473+
if (jsonPointerStr != null) {
474+
jsonPointer.set(null)
475+
}
476+
477+
where:
478+
service | operation | method | path | client | call | additionalTags | body | jsonPointerStr
479+
"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
480+
"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"] | """<CreateQueueResponse><CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/test-queue</QueueUrl></CreateQueueResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></CreateQueueResponse>""" | "/CreateQueueResponse/CreateQueueResult"
481+
"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"] | """<SendMessageResponse><SendMessageResult><MD5OfMessageBody>098f6bcd4621d373cade4e832627b4f6</MD5OfMessageBody><MessageId>test-msg-id</MessageId></SendMessageResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></SendMessageResponse>""" | "/SendMessageResponse/SendMessageResult"
482+
"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"] | """<PublishResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/"><PublishResult><MessageId>test-msg-id</MessageId></PublishResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></PublishResponse>""" | "/PublishResponse/PublishResult"
483+
"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
484+
"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
485+
}
404486
}
405487

406488
class AWS1ClientV0Test extends AWS1ClientTest {

dd-java-agent/instrumentation/aws-java-sdk-2.2/src/main/java/datadog/trace/instrumentation/aws/v2/AwsSdkClientDecorator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,18 @@ public AgentSpan onSdkRequest(
208208
}
209209
}
210210

211+
// Set peer.service based on Config for serverless functions
212+
if (Config.get().isAwsServerless()) {
213+
URI uri = httpRequest.getUri();
214+
String hostname = uri.getHost();
215+
if (uri.getPort() != -1) {
216+
hostname = hostname + ":" + uri.getPort();
217+
}
218+
219+
span.setTag(Tags.PEER_SERVICE, hostname);
220+
span.setTag(DDTags.PEER_SERVICE_SOURCE, "peer.service");
221+
}
222+
211223
return span;
212224
}
213225

dd-java-agent/instrumentation/aws-java-sdk-2.2/src/test/groovy/Aws2ClientTest.groovy

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,112 @@ abstract class Aws2ClientTest extends VersionedNamingTestBase {
421421
cleanup:
422422
server.close()
423423
}
424+
425+
def "#service #operation sets peer.service in serverless environment"() {
426+
setup:
427+
428+
if (version() == 0) {
429+
return
430+
}
431+
432+
// Set the AWS Lambda function name environment variable
433+
injectEnvConfig("AWS_LAMBDA_FUNCTION_NAME", "my-test-lambda-function", false)
434+
435+
// Create client with mocked endpoint
436+
def client = builder
437+
.endpointOverride(server.address)
438+
.region(Region.US_EAST_1)
439+
.credentialsProvider(CREDENTIALS_PROVIDER)
440+
.build()
441+
442+
// Set response body
443+
responseBody.set(body)
444+
445+
when:
446+
// Make the request
447+
def response = call.call(client)
448+
449+
if (response instanceof Future) {
450+
response = response.get()
451+
}
452+
453+
// Wait for traces to be written
454+
TEST_WRITER.waitForTraces(1)
455+
456+
then:
457+
response != null
458+
459+
// Verify the trace
460+
assertTraces(1) {
461+
trace(1) {
462+
span {
463+
serviceName expectedService(service, operation)
464+
operationName expectedOperation(service, operation)
465+
resourceName "$service.$operation"
466+
spanType DDSpanTypes.HTTP_CLIENT
467+
errored false
468+
measured true
469+
parent()
470+
tags {
471+
defaultTags(false, true)
472+
473+
// AWS specific tags
474+
"aws.service" service
475+
"aws_service" service
476+
"aws.operation" operation
477+
"aws.agent" "java-aws-sdk"
478+
"aws.requestId" requestId
479+
480+
// HTTP tags
481+
"$Tags.HTTP_METHOD" method
482+
"$Tags.HTTP_STATUS" 200
483+
"$Tags.HTTP_URL" String
484+
485+
// Peer tags
486+
"$Tags.PEER_HOSTNAME" "localhost"
487+
"$Tags.PEER_PORT" server.address.port
488+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
489+
"$Tags.COMPONENT" "java-aws-sdk"
490+
491+
// Service-specific tags
492+
if (service == "S3") {
493+
"aws.bucket.name" "test-bucket"
494+
"bucketname" "test-bucket"
495+
} else if (service == "Sqs" && operation == "CreateQueue") {
496+
"aws.queue.name" "test-queue"
497+
"queuename" "test-queue"
498+
} else if (service == "Sqs" && operation == "SendMessage") {
499+
"aws.queue.url" "test-queue-url"
500+
} else if (service == "Sns" && operation == "Publish") {
501+
"aws.topic.name" "test-topic"
502+
"topicname" "test-topic"
503+
} else if (service == "DynamoDb") {
504+
"aws.table.name" "test-table"
505+
"tablename" "test-table"
506+
} else if (service == "Kinesis") {
507+
"aws.stream.name" "test-stream"
508+
"streamname" "test-stream"
509+
}
510+
511+
urlTags("${server.address}${path}", ExpectedQueryParams.getExpectedQueryParams(operation))
512+
513+
// Test specific peer service assertions in serverless
514+
"peer.service" "${server.address.host}:${server.address.port}"
515+
"_dd.peer.service.source" "peer.service"
516+
}
517+
}
518+
}
519+
}
520+
521+
where:
522+
service | operation | method | path | builder | call | body | requestId
523+
"S3" | "CreateBucket" | "PUT" | "/test-bucket" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("test-bucket").build()) } | "" | "UNKNOWN"
524+
"Sqs" | "CreateQueue" | "POST" | "/" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("test-queue").build()) } | """<CreateQueueResponse><CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/test-queue</QueueUrl></CreateQueueResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></CreateQueueResponse>""" | "test-request-id"
525+
"Sqs" | "SendMessage" | "POST" | "/" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("test-queue-url").messageBody("test").build()) } | """<SendMessageResponse><SendMessageResult><MD5OfMessageBody>098f6bcd4621d373cade4e832627b4f6</MD5OfMessageBody><MessageId>test-msg-id</MessageId></SendMessageResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></SendMessageResponse>""" | "test-request-id"
526+
"Sns" | "Publish" | "POST" | "/" | SnsClient.builder() | { c -> c.publish(PublishRequest.builder().topicArn("arn:aws:sns::123:test-topic").message("test").build()) } | """<PublishResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/"><PublishResult><MessageId>test-msg-id</MessageId></PublishResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></PublishResponse>""" | "test-request-id"
527+
"DynamoDb" | "CreateTable" | "POST" | "/" | DynamoDbClient.builder() | { c -> c.createTable(CreateTableRequest.builder().tableName("test-table").build()) } | "" | "UNKNOWN"
528+
"Kinesis" | "DeleteStream" | "POST" | "/" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("test-stream").build()) } | "" | "UNKNOWN"
529+
}
424530
}
425531

426532
class Aws2ClientV0ForkedTest extends Aws2ClientTest {

internal-api/src/main/java/datadog/trace/api/Config.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,7 @@ public static String getHostName() {
11681168

11691169
private final boolean azureAppServices;
11701170
private final boolean azureFunctions;
1171+
private final boolean awsServerless;
11711172
private final String traceAgentPath;
11721173
private final List<String> traceAgentArgs;
11731174
private final String dogStatsDPath;
@@ -1487,6 +1488,9 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins
14871488
azureFunctions =
14881489
getEnv("FUNCTIONS_WORKER_RUNTIME") != null && getEnv("FUNCTIONS_EXTENSION_VERSION") != null;
14891490

1491+
awsServerless =
1492+
getEnv("AWS_LAMBDA_FUNCTION_NAME") != null && !getEnv("AWS_LAMBDA_FUNCTION_NAME").isEmpty();
1493+
14901494
spanAttributeSchemaVersion = schemaVersionFromConfig();
14911495

14921496
peerHostNameEnabled = configProvider.getBoolean(TRACE_PEER_HOSTNAME_ENABLED, true);
@@ -4270,6 +4274,10 @@ public boolean isAzureAppServices() {
42704274
return azureAppServices;
42714275
}
42724276

4277+
public boolean isAwsServerless() {
4278+
return awsServerless;
4279+
}
4280+
42734281
public boolean isDataStreamsEnabled() {
42744282
return dataStreamsEnabled;
42754283
}

0 commit comments

Comments
 (0)