Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"] | """<CreateQueueResponse><CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/test-queue</QueueUrl></CreateQueueResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></CreateQueueResponse>""" | "/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"] | """<SendMessageResponse><SendMessageResult><MD5OfMessageBody>098f6bcd4621d373cade4e832627b4f6</MD5OfMessageBody><MessageId>test-msg-id</MessageId></SendMessageResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></SendMessageResponse>""" | "/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"] | """<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"
"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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) } | """<CreateQueueResponse><CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/test-queue</QueueUrl></CreateQueueResult><ResponseMetadata><RequestId>test-request-id</RequestId></ResponseMetadata></CreateQueueResponse>""" | "test-request-id"
"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"
"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"
"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 {
Expand Down
8 changes: 8 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> traceAgentArgs;
private final String dogStatsDPath;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -4270,6 +4274,10 @@ public boolean isAzureAppServices() {
return azureAppServices;
}

public boolean isAwsServerless() {
return awsServerless;
}

public boolean isDataStreamsEnabled() {
return dataStreamsEnabled;
}
Expand Down
Loading