diff --git a/.github/patches/opentelemetry-java-instrumentation.patch b/.github/patches/opentelemetry-java-instrumentation.patch index 1de3294474..280bd01bad 100644 --- a/.github/patches/opentelemetry-java-instrumentation.patch +++ b/.github/patches/opentelemetry-java-instrumentation.patch @@ -1,42 +1,38 @@ diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt -index 93437ef1e0..4e9248fd01 100644 +index 93437ef1e0..3f564d25bc 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt @@ -1,2 +1,2 @@ --Comparing source compatibility of opentelemetry-instrumentation-annotations-2.11.0.jar against opentelemetry-instrumentation-annotations-2.10.0.jar + Comparing source compatibility of opentelemetry-instrumentation-annotations-2.11.0.jar against opentelemetry-instrumentation-annotations-2.10.0.jar -No changes. \ No newline at end of file -+Comparing source compatibility of opentelemetry-instrumentation-annotations-2.11.0-adot1.jar against opentelemetry-instrumentation-annotations-2.11.0.jar +No changes. diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt -index d759eed30a..1c725a0a25 100644 +index d759eed30a..385bd90663 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt @@ -1,2 +1,2 @@ --Comparing source compatibility of opentelemetry-instrumentation-api-2.11.0.jar against opentelemetry-instrumentation-api-2.10.0.jar + Comparing source compatibility of opentelemetry-instrumentation-api-2.11.0.jar against opentelemetry-instrumentation-api-2.10.0.jar -No changes. \ No newline at end of file -+Comparing source compatibility of opentelemetry-instrumentation-api-2.11.0-adot1.jar against opentelemetry-instrumentation-api-2.11.0.jar +No changes. diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt -index f657f219ae..a6ec574fe5 100644 +index f657f219ae..2b4a59db8f 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt @@ -1,2 +1,2 @@ --Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.11.0.jar against opentelemetry-spring-boot-autoconfigure-2.10.0.jar + Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.11.0.jar against opentelemetry-spring-boot-autoconfigure-2.10.0.jar -No changes. \ No newline at end of file -+Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.11.0-adot1.jar against opentelemetry-spring-boot-autoconfigure-2.11.0.jar +No changes. diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-starter.txt b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-starter.txt -index 02f520fd45..2109c5a927 100644 +index 02f520fd45..99505334b7 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-starter.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-starter.txt @@ -1,2 +1,2 @@ --Comparing source compatibility of opentelemetry-spring-boot-starter-2.11.0.jar against opentelemetry-spring-boot-starter-2.10.0.jar + Comparing source compatibility of opentelemetry-spring-boot-starter-2.11.0.jar against opentelemetry-spring-boot-starter-2.10.0.jar -No changes. \ No newline at end of file -+Comparing source compatibility of opentelemetry-spring-boot-starter-2.11.0-adot1.jar against opentelemetry-spring-boot-starter-2.11.0.jar +No changes. diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts index f357a19f88..fa90530579 100644 @@ -58,58 +54,132 @@ index f357a19f88..fa90530579 100644 testImplementation(project(":instrumentation:aws-sdk:aws-sdk-1.11:testing")) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSpanAssertions.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSpanAssertions.java -index 483a0c5230..2415577e37 100644 +index 483a0c5230..5b1ee9ac4a 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSpanAssertions.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSpanAssertions.java -@@ -85,11 +85,35 @@ class AwsSpanAssertions { +@@ -37,6 +37,7 @@ class AwsSpanAssertions { + satisfies(stringKey("aws.endpoint"), v -> v.isInstanceOf(String.class)), + equalTo(stringKey("aws.queue.name"), queueName), + equalTo(stringKey("aws.queue.url"), queueUrl), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), + satisfies(AWS_REQUEST_ID, v -> v.isInstanceOf(String.class)), + equalTo(RPC_METHOD, rpcMethod), + equalTo(RPC_SYSTEM, "aws-api"), +@@ -71,6 +72,7 @@ class AwsSpanAssertions { + equalTo(RPC_METHOD, rpcMethod), + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "Amazon S3"), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), + equalTo(HTTP_REQUEST_METHOD, requestMethod), + equalTo(HTTP_RESPONSE_STATUS_CODE, responseStatusCode), + satisfies(URL_FULL, val -> val.startsWith("http://")), +@@ -85,28 +87,52 @@ class AwsSpanAssertions { } static SpanDataAssert sns(SpanDataAssert span, String topicArn, String rpcMethod) { -- ++ SpanDataAssert spanAssert = ++ span.hasName("SNS." + rpcMethod).hasKind(SpanKind.CLIENT).hasNoParent(); + - return span.hasName("SNS." + rpcMethod) -+ SpanDataAssert spanAssert = span.hasName("SNS." + rpcMethod) - .hasKind(SpanKind.CLIENT) +- .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasAttributesSatisfyingExactly( -+ .hasNoParent(); -+ -+ // For CreateTopic, the topicArn parameter might be null but aws.sns.topic.arn +- equalTo(stringKey("aws.agent"), "java-aws-sdk"), +- equalTo(MESSAGING_DESTINATION_NAME, topicArn), +- satisfies(stringKey("aws.endpoint"), v -> v.isInstanceOf(String.class)), +- satisfies(AWS_REQUEST_ID, v -> v.isInstanceOf(String.class)), +- equalTo(RPC_METHOD, rpcMethod), +- equalTo(RPC_SYSTEM, "aws-api"), +- equalTo(RPC_SERVICE, "AmazonSNS"), +- equalTo(HTTP_REQUEST_METHOD, "POST"), +- equalTo(HTTP_RESPONSE_STATUS_CODE, 200), +- satisfies(URL_FULL, val -> val.startsWith("http://")), +- satisfies(SERVER_ADDRESS, v -> v.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), +- satisfies( +- SERVER_PORT, +- val -> +- val.satisfiesAnyOf( +- v -> assertThat(v).isNull(), +- v -> assertThat(v).isInstanceOf(Number.class)))); ++ // For CreateTopic, the topicArn parameter might be null but aws.sns.topic.arn + // will be set from the response + if ("CreateTopic".equals(rpcMethod)) { + return spanAssert.hasAttributesSatisfyingExactly( -+ equalTo(stringKey("aws.agent"), "java-aws-sdk"), -+ satisfies(stringKey("aws.endpoint"), v -> v.isInstanceOf(String.class)), -+ satisfies(AWS_REQUEST_ID, v -> v.isInstanceOf(String.class)), -+ equalTo(RPC_METHOD, rpcMethod), -+ equalTo(RPC_SYSTEM, "aws-api"), -+ equalTo(RPC_SERVICE, "AmazonSNS"), -+ equalTo(HTTP_REQUEST_METHOD, "POST"), -+ equalTo(HTTP_RESPONSE_STATUS_CODE, 200), -+ satisfies(URL_FULL, val -> val.startsWith("http://")), -+ satisfies(SERVER_ADDRESS, v -> v.isInstanceOf(String.class)), -+ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), -+ satisfies( -+ SERVER_PORT, -+ val -> -+ val.satisfiesAnyOf( -+ v -> assertThat(v).isNull(), -+ v -> assertThat(v).isInstanceOf(Number.class))), -+ satisfies(stringKey("aws.sns.topic.arn"), v -> v.isInstanceOf(String.class))); ++ equalTo(stringKey("aws.agent"), "java-aws-sdk"), ++ satisfies(stringKey("aws.endpoint"), v -> v.isInstanceOf(String.class)), ++ satisfies(AWS_REQUEST_ID, v -> v.isInstanceOf(String.class)), ++ equalTo(RPC_METHOD, rpcMethod), ++ equalTo(RPC_SYSTEM, "aws-api"), ++ equalTo(RPC_SERVICE, "AmazonSNS"), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), ++ equalTo(HTTP_REQUEST_METHOD, "POST"), ++ equalTo(HTTP_RESPONSE_STATUS_CODE, 200), ++ satisfies(URL_FULL, val -> val.startsWith("http://")), ++ satisfies(SERVER_ADDRESS, v -> v.isInstanceOf(String.class)), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ satisfies( ++ SERVER_PORT, ++ val -> ++ val.satisfiesAnyOf( ++ v -> assertThat(v).isNull(), v -> assertThat(v).isInstanceOf(Number.class))), ++ satisfies(stringKey("aws.sns.topic.arn"), v -> v.isInstanceOf(String.class))); + } + + return spanAssert.hasAttributesSatisfyingExactly( - equalTo(stringKey("aws.agent"), "java-aws-sdk"), - equalTo(MESSAGING_DESTINATION_NAME, topicArn), - satisfies(stringKey("aws.endpoint"), v -> v.isInstanceOf(String.class)), -@@ -107,6 +131,7 @@ class AwsSpanAssertions { - val -> - val.satisfiesAnyOf( - v -> assertThat(v).isNull(), -- v -> assertThat(v).isInstanceOf(Number.class)))); -+ v -> assertThat(v).isInstanceOf(Number.class))), -+ equalTo(stringKey("aws.sns.topic.arn"), topicArn)); ++ equalTo(stringKey("aws.agent"), "java-aws-sdk"), ++ equalTo(MESSAGING_DESTINATION_NAME, topicArn), ++ satisfies(stringKey("aws.endpoint"), v -> v.isInstanceOf(String.class)), ++ satisfies(AWS_REQUEST_ID, v -> v.isInstanceOf(String.class)), ++ equalTo(RPC_METHOD, rpcMethod), ++ equalTo(RPC_SYSTEM, "aws-api"), ++ equalTo(RPC_SERVICE, "AmazonSNS"), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), ++ equalTo(HTTP_REQUEST_METHOD, "POST"), ++ equalTo(HTTP_RESPONSE_STATUS_CODE, 200), ++ satisfies(URL_FULL, val -> val.startsWith("http://")), ++ satisfies(SERVER_ADDRESS, v -> v.isInstanceOf(String.class)), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ satisfies( ++ SERVER_PORT, ++ val -> ++ val.satisfiesAnyOf( ++ v -> assertThat(v).isNull(), v -> assertThat(v).isInstanceOf(Number.class))), ++ equalTo(stringKey("aws.sns.topic.arn"), topicArn)); } } +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/S3TracingTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/S3TracingTest.java +index 56eca09f8c..82c3379840 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/S3TracingTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/S3TracingTest.java +@@ -105,6 +105,7 @@ class S3TracingTest { + equalTo(RPC_METHOD, "ReceiveMessage"), + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "AmazonSQS"), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + satisfies(URL_FULL, val -> val.startsWith("http://")), +@@ -198,6 +199,7 @@ class S3TracingTest { + equalTo(RPC_METHOD, "ReceiveMessage"), + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "AmazonSQS"), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + satisfies(URL_FULL, val -> val.startsWith("http://")), +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SnsTracingTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SnsTracingTest.java +index 429ca07938..d21918bc70 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SnsTracingTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SnsTracingTest.java +@@ -89,6 +89,7 @@ class SnsTracingTest { + equalTo(RPC_METHOD, "ReceiveMessage"), + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "AmazonSQS"), ++ equalTo(stringKey("aws.auth.account.access_key"), "test"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + satisfies(URL_FULL, val -> val.startsWith("http://")), diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/library-autoconfigure/build.gradle.kts index 6cf49a21c4..3705634153 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library-autoconfigure/build.gradle.kts @@ -287,13 +357,16 @@ index 0000000000..e890cb3c0f + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java -index 096c7826a1..a271b16da8 100644 +index 096c7826a1..27613c04f2 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java -@@ -17,6 +17,36 @@ final class AwsExperimentalAttributes { +@@ -16,7 +16,41 @@ final class AwsExperimentalAttributes { + static final AttributeKey AWS_QUEUE_URL = stringKey("aws.queue.url"); static final AttributeKey AWS_QUEUE_NAME = stringKey("aws.queue.name"); static final AttributeKey AWS_STREAM_NAME = stringKey("aws.stream.name"); ++ static final AttributeKey AWS_STREAM_ARN = stringKey("aws.stream.arn"); static final AttributeKey AWS_TABLE_NAME = stringKey("aws.table.name"); ++ static final AttributeKey AWS_TABLE_ARN = stringKey("aws.table.arn"); + static final AttributeKey AWS_AGENT_ID = stringKey("aws.bedrock.agent.id"); + static final AttributeKey AWS_KNOWLEDGE_BASE_ID = + stringKey("aws.bedrock.knowledge_base.id"); @@ -322,20 +395,23 @@ index 096c7826a1..a271b16da8 100644 + static final AttributeKey AWS_SNS_TOPIC_ARN = stringKey("aws.sns.topic.arn"); + static final AttributeKey AWS_SECRET_ARN = stringKey("aws.secretsmanager.secret.arn"); + static final AttributeKey AWS_LAMBDA_NAME = stringKey("aws.lambda.function.name"); ++ static final AttributeKey AWS_LAMBDA_ARN = stringKey("aws.lambda.function.arn"); + static final AttributeKey AWS_LAMBDA_RESOURCE_ID = + stringKey("aws.lambda.resource_mapping.id"); ++ static final AttributeKey AWS_AUTH_ACCESS_KEY = stringKey("aws.auth.account.access_key"); private AwsExperimentalAttributes() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java -index 541e67d23b..1abf8e9c28 100644 +index 541e67d23b..5a321f9cb1 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java -@@ -6,12 +6,30 @@ +@@ -6,25 +6,56 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AGENT; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AGENT_ID; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AUTH_ACCESS_KEY; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_BEDROCK_RUNTIME_MODEL_ID; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_BEDROCK_SYSTEM; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_BUCKET_NAME; @@ -343,6 +419,7 @@ index 541e67d23b..1abf8e9c28 100644 +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_GUARDRAIL_ARN; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_GUARDRAIL_ID; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_KNOWLEDGE_BASE_ID; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_LAMBDA_ARN; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_LAMBDA_NAME; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_LAMBDA_RESOURCE_ID; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_QUEUE_NAME; @@ -351,7 +428,9 @@ index 541e67d23b..1abf8e9c28 100644 +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_SNS_TOPIC_ARN; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_STATE_MACHINE_ARN; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_STEP_FUNCTIONS_ACTIVITY_ARN; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_STREAM_ARN; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_STREAM_NAME; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_TABLE_ARN; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_TABLE_NAME; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_REQUEST_MAX_TOKENS; +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_REQUEST_TEMPERATURE; @@ -362,7 +441,9 @@ index 541e67d23b..1abf8e9c28 100644 import com.amazonaws.Request; import com.amazonaws.Response; -@@ -19,12 +37,17 @@ import io.opentelemetry.api.common.AttributeKey; ++import com.amazonaws.auth.AWSCredentials; ++import com.amazonaws.handlers.HandlerContextKey; + import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -377,10 +458,12 @@ index 541e67d23b..1abf8e9c28 100644 + private static final String BEDROCK_AGENT_SERVICE = "AWSBedrockAgent"; + private static final String BEDROCK_AGENT_RUNTIME_SERVICE = "AWSBedrockAgentRuntime"; + private static final String BEDROCK_RUNTIME_SERVICE = "AmazonBedrockRuntime"; ++ private static final HandlerContextKey AWS_CREDENTIALS = ++ new HandlerContextKey("AWSCredentials"); @Override public void onStart(AttributesBuilder attributes, Context parentContext, Request request) { -@@ -32,14 +55,155 @@ class AwsSdkExperimentalAttributesExtractor +@@ -32,14 +63,165 @@ class AwsSdkExperimentalAttributesExtractor attributes.put(AWS_ENDPOINT, request.getEndpoint().toString()); Object originalRequest = request.getOriginalRequest(); @@ -390,10 +473,18 @@ index 541e67d23b..1abf8e9c28 100644 - setRequestAttribute(attributes, AWS_STREAM_NAME, originalRequest, RequestAccess::getStreamName); - setRequestAttribute(attributes, AWS_TABLE_NAME, originalRequest, RequestAccess::getTableName); + String requestClassName = originalRequest.getClass().getSimpleName(); ++ AWSCredentials credentials = request.getHandlerContext(AWS_CREDENTIALS); ++ if (credentials != null) { ++ String accessKeyId = credentials.getAWSAccessKeyId(); ++ if (accessKeyId != null) { ++ attributes.put(AWS_AUTH_ACCESS_KEY, accessKeyId); ++ } ++ } + setAttribute(attributes, AWS_BUCKET_NAME, originalRequest, RequestAccess::getBucketName); + setAttribute(attributes, AWS_QUEUE_URL, originalRequest, RequestAccess::getQueueUrl); + setAttribute(attributes, AWS_QUEUE_NAME, originalRequest, RequestAccess::getQueueName); + setAttribute(attributes, AWS_STREAM_NAME, originalRequest, RequestAccess::getStreamName); ++ setAttribute(attributes, AWS_STREAM_ARN, originalRequest, RequestAccess::getStreamArn); + setAttribute(attributes, AWS_TABLE_NAME, originalRequest, RequestAccess::getTableName); + setAttribute( + attributes, AWS_STATE_MACHINE_ARN, originalRequest, RequestAccess::getStateMachineArn); @@ -413,8 +504,9 @@ index 541e67d23b..1abf8e9c28 100644 + if (isBedrockService(serviceName)) { + bedrockOnStart(attributes, originalRequest, requestClassName, serviceName); + } -+ } -+ + } + +- private static void setRequestAttribute( + @Override + public void onEnd( + AttributesBuilder attributes, @@ -424,6 +516,8 @@ index 541e67d23b..1abf8e9c28 100644 + @Nullable Throwable error) { + if (response != null) { + Object awsResp = response.getAwsResponse(); ++ setAttribute(attributes, AWS_TABLE_ARN, awsResp, RequestAccess::getTableArn); ++ setAttribute(attributes, AWS_LAMBDA_ARN, awsResp, RequestAccess::getLambdaArn); + setAttribute(attributes, AWS_STATE_MACHINE_ARN, awsResp, RequestAccess::getStateMachineArn); + setAttribute( + attributes, @@ -535,14 +629,13 @@ index 541e67d23b..1abf8e9c28 100644 + || serviceName.equals(BEDROCK_AGENT_SERVICE) + || serviceName.equals(BEDROCK_AGENT_RUNTIME_SERVICE) + || serviceName.equals(BEDROCK_RUNTIME_SERVICE); - } - -- private static void setRequestAttribute( ++ } ++ + private static void setAttribute( AttributesBuilder attributes, AttributeKey key, Object request, -@@ -49,12 +213,4 @@ class AwsSdkExperimentalAttributesExtractor +@@ -49,12 +231,4 @@ class AwsSdkExperimentalAttributesExtractor attributes.put(key, value); } } @@ -829,7 +922,7 @@ index 0000000000..d1acc5768a + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java -index c212a69678..3101685194 100644 +index c212a69678..82a7185abe 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java @@ -8,6 +8,12 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; @@ -845,7 +938,7 @@ index c212a69678..3101685194 100644 import javax.annotation.Nullable; final class RequestAccess { -@@ -20,48 +26,392 @@ final class RequestAccess { +@@ -20,48 +26,417 @@ final class RequestAccess { } }; @@ -1075,6 +1168,14 @@ index c212a69678..3101685194 100644 + } + + @Nullable ++ static String getLambdaArn(Object request) { ++ if (request == null) { ++ return null; ++ } ++ return findNestedAccessorOrNull(request, "getConfiguration", "getFunctionArn"); ++ } ++ ++ @Nullable + static String getLambdaResourceId(Object request) { + if (request == null) { + return null; @@ -1164,6 +1265,23 @@ index c212a69678..3101685194 100644 return invokeOrNull(access.getTableName, request); } ++ @Nullable ++ static String getTableArn(Object request) { ++ if (request == null) { ++ return null; ++ } ++ return findNestedAccessorOrNull(request, "getTable", "getTableArn"); ++ } ++ ++ @Nullable ++ static String getStreamArn(Object request) { ++ if (request == null) { ++ return null; ++ } ++ RequestAccess access = REQUEST_ACCESSORS.get(request.getClass()); ++ return invokeOrNull(access.getStreamArn, request); ++ } ++ @Nullable static String getTopicArn(Object request) { + if (request == null) { @@ -1238,7 +1356,7 @@ index c212a69678..3101685194 100644 @Nullable private static String invokeOrNull(@Nullable MethodHandle method, Object obj) { if (method == null) { -@@ -74,6 +424,19 @@ final class RequestAccess { +@@ -74,31 +449,88 @@ final class RequestAccess { } } @@ -1258,7 +1376,8 @@ index c212a69678..3101685194 100644 @Nullable private final MethodHandle getBucketName; @Nullable private final MethodHandle getQueueUrl; @Nullable private final MethodHandle getQueueName; -@@ -81,24 +444,66 @@ final class RequestAccess { + @Nullable private final MethodHandle getStreamName; ++ @Nullable private final MethodHandle getStreamArn; @Nullable private final MethodHandle getTableName; @Nullable private final MethodHandle getTopicArn; @Nullable private final MethodHandle getTargetArn; @@ -1287,6 +1406,7 @@ index c212a69678..3101685194 100644 + getQueueUrl = findAccessorOrNull(clz, "getQueueUrl", String.class); + getQueueName = findAccessorOrNull(clz, "getQueueName", String.class); + getStreamName = findAccessorOrNull(clz, "getStreamName", String.class); ++ getStreamArn = findAccessorOrNull(clz, "getStreamARN", String.class); + getTableName = findAccessorOrNull(clz, "getTableName", String.class); + getTopicArn = findAccessorOrNull(clz, "getTopicArn", String.class); + getTargetArn = findAccessorOrNull(clz, "getTargetArn", String.class); @@ -1793,6 +1913,126 @@ index 0000000000..98a5873614 + }); + } +} +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java +index 441a4a3a0b..529e317a65 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java +@@ -11,10 +11,12 @@ import static io.opentelemetry.semconv.incubating.AwsIncubatingAttributes.AWS_DY + import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; + import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemIncubatingValues.DYNAMODB; + import static java.util.Collections.singletonList; ++import static org.junit.Assert.assertEquals; + + import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; + import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; + import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; ++import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; + import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; + import io.opentelemetry.testing.internal.armeria.common.HttpResponse; + import io.opentelemetry.testing.internal.armeria.common.HttpStatus; +@@ -53,4 +55,39 @@ public abstract class AbstractDynamoDbClientTest extends AbstractBaseAwsClientTe + assertRequestWithMockedResponse( + response, client, "DynamoDBv2", "CreateTable", "POST", additionalAttributes); + } ++ ++ @Test ++ public void testGetTableArnWithMockedResponse() { ++ AmazonDynamoDBClientBuilder clientBuilder = AmazonDynamoDBClientBuilder.standard(); ++ AmazonDynamoDB client = ++ configureClient(clientBuilder) ++ .withEndpointConfiguration(endpoint) ++ .withCredentials(credentialsProvider) ++ .build(); ++ ++ String tableName = "MockTable"; ++ String expectedArn = "arn:aws:dynamodb:us-west-2:123456789012:table/" + tableName; ++ ++ String body = ++ "{\n" ++ + "\"Table\": {\n" ++ + "\"TableName\": \"" ++ + tableName ++ + "\",\n" ++ + "\"TableArn\": \"" ++ + expectedArn ++ + "\"\n" ++ + "}\n" ++ + "}"; ++ ++ server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, body)); ++ ++ String actualArn = ++ client ++ .describeTable(new DescribeTableRequest().withTableName(tableName)) ++ .getTable() ++ .getTableArn(); ++ ++ assertEquals("Table ARN should match expected value", expectedArn, actualArn); ++ } + } +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractKinesisClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractKinesisClientTest.java +index ee6d1b7501..a21b1ebefa 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractKinesisClientTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractKinesisClientTest.java +@@ -12,13 +12,16 @@ import static java.util.Collections.singletonList; + import com.amazonaws.services.kinesis.AmazonKinesis; + import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; + import com.amazonaws.services.kinesis.model.DeleteStreamRequest; ++import com.amazonaws.services.kinesis.model.DescribeStreamRequest; + import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; + import io.opentelemetry.testing.internal.armeria.common.HttpResponse; + import io.opentelemetry.testing.internal.armeria.common.HttpStatus; + import io.opentelemetry.testing.internal.armeria.common.MediaType; ++import java.util.Arrays; + import java.util.List; + import java.util.function.Function; + import java.util.stream.Stream; ++import org.junit.Test; + import org.junit.jupiter.params.ParameterizedTest; + import org.junit.jupiter.params.provider.Arguments; + import org.junit.jupiter.params.provider.MethodSource; +@@ -54,6 +57,41 @@ public abstract class AbstractKinesisClientTest extends AbstractBaseAwsClientTes + response, client, "Kinesis", operation, "POST", additionalAttributes); + } + ++ @Test ++ public void sendRequestWithStreamArnMockedResponse() throws Exception { ++ AmazonKinesisClientBuilder clientBuilder = AmazonKinesisClientBuilder.standard(); ++ AmazonKinesis client = ++ configureClient(clientBuilder) ++ .withEndpointConfiguration(endpoint) ++ .withCredentials(credentialsProvider) ++ .build(); ++ ++ String body = ++ "{\n" ++ + "\"StreamDescription\": {\n" ++ + "\"StreamARN\": \"arn:aws:kinesis:us-east-1:123456789012:stream/somestream\",\n" ++ + "\"StreamName\": \"somestream\",\n" ++ + "\"StreamStatus\": \"ACTIVE\",\n" ++ + "\"Shards\": []\n" ++ + "}\n" ++ + "}"; ++ ++ server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, body)); ++ ++ List additionalAttributes = ++ Arrays.asList( ++ equalTo(stringKey("aws.stream.name"), "somestream"), ++ equalTo( ++ stringKey("aws.stream.arn"), ++ "arn:aws:kinesis:us-east-1:123456789012:stream/somestream")); ++ ++ Object response = ++ client.describeStream(new DescribeStreamRequest().withStreamName("somestream")); ++ ++ assertRequestWithMockedResponse( ++ response, client, "Kinesis", "DescribeStream", "POST", additionalAttributes); ++ } ++ + private static Stream provideArguments() { + return Stream.of( + Arguments.of( diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractLambdaClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractLambdaClientTest.java new file mode 100644 index 0000000000..9f5a245ee7 @@ -1871,6 +2111,18 @@ index 0000000000..9f5a245ee7 + c -> c.getFunction(new GetFunctionRequest().withFunctionName("functionName")))); + } +} +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java +index 574165992f..5248d050b6 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java +@@ -175,6 +175,7 @@ public abstract class AbstractS3ClientTest extends AbstractBaseAwsClientTest { + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "Amazon S3"), + equalTo(RPC_METHOD, "GetObject"), ++ equalTo(stringKey("aws.auth.account.access_key"), "my-access-key"), + equalTo(stringKey("aws.endpoint"), server.httpUri().toString()), + equalTo(stringKey("aws.agent"), "java-aws-sdk"), + equalTo(stringKey("aws.bucket.name"), "someBucket"), diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSecretsManagerClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSecretsManagerClientTest.java new file mode 100644 index 0000000000..03de6fce3f @@ -2038,6 +2290,174 @@ index 3f272ba477..bea20f3d86 100644 + response, client, "SNS", "Publish", "POST", additionalAttributes); } } +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java +index c0b4b13a17..4cfaf469d9 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java +@@ -116,7 +116,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(URL_FULL, "http://localhost:" + sqsPort), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> +@@ -146,7 +147,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(MESSAGING_OPERATION, "publish"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("testSdkSqs process") + .hasKind(SpanKind.CONSUMER) +@@ -174,7 +176,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(MESSAGING_OPERATION, "process"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("process child") + .hasParent(trace.getSpan(1)) +@@ -222,7 +225,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(URL_FULL, "http://localhost:" + sqsPort), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> +@@ -252,7 +256,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(MESSAGING_OPERATION, "publish"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("testSdkSqs process") + .hasKind(SpanKind.CONSUMER) +@@ -280,7 +285,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(MESSAGING_OPERATION, "process"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("process child") + .hasParent(trace.getSpan(1)) +@@ -311,7 +317,8 @@ public abstract class AbstractSqsSuppressReceiveSpansTest { + equalTo(URL_FULL, "http://localhost:" + sqsPort), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")))); ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")))); + } + + @Test +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java +index f1bfa126ca..dfb5b96550 100644 +--- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java +@@ -150,7 +150,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(URL_FULL, "http://localhost:" + sqsPort), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> { +@@ -179,7 +180,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(MESSAGING_OPERATION, "publish"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))); ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))); + + if (testCaptureHeaders) { + attributes.add( +@@ -220,7 +222,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(MESSAGING_DESTINATION_NAME, "testSdkSqs"), + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))); ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))); + + if (testCaptureHeaders) { + attributes.add( +@@ -260,7 +263,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(MESSAGING_OPERATION, "process"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))); ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))); + + if (testCaptureHeaders) { + attributes.add( +@@ -320,7 +324,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(URL_FULL, "http://localhost:" + sqsPort), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> +@@ -350,7 +355,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(MESSAGING_OPERATION, "publish"), + satisfies( + MESSAGING_MESSAGE_ID, val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1"))), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))), + trace -> { + AtomicReference receiveSpan = new AtomicReference<>(); + AtomicReference processSpan = new AtomicReference<>(); +@@ -385,7 +391,8 @@ public abstract class AbstractSqsTracingTest { + equalTo(URL_FULL, "http://localhost:" + sqsPort), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("testSdkSqs receive") + .hasKind(SpanKind.CONSUMER) +@@ -419,7 +426,8 @@ public abstract class AbstractSqsTracingTest { + MessagingIncubatingAttributes + .MESSAGING_BATCH_MESSAGE_COUNT, + 1), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("testSdkSqs process") + .hasKind(SpanKind.CONSUMER) +@@ -452,7 +460,8 @@ public abstract class AbstractSqsTracingTest { + satisfies( + MESSAGING_MESSAGE_ID, + val -> val.isInstanceOf(String.class)), +- equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), ++ equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x")), + span -> + span.hasName("process child") + .hasParent(processSpan.get()) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractStepFunctionsClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractStepFunctionsClientTest.java new file mode 100644 index 0000000000..fc58ec3c9b @@ -2166,10 +2586,10 @@ index 3b7381a8ba..6f77951710 100644 testing { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsExperimentalAttributes.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsExperimentalAttributes.java new file mode 100644 -index 0000000000..4aed4a58c0 +index 0000000000..fd951ffe37 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsExperimentalAttributes.java -@@ -0,0 +1,73 @@ +@@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 @@ -2186,6 +2606,7 @@ index 0000000000..4aed4a58c0 + static final AttributeKey AWS_QUEUE_URL = stringKey("aws.queue.url"); + static final AttributeKey AWS_QUEUE_NAME = stringKey("aws.queue.name"); + static final AttributeKey AWS_STREAM_NAME = stringKey("aws.stream.name"); ++ static final AttributeKey AWS_STREAM_ARN = stringKey("aws.stream.arn"); + static final AttributeKey AWS_TABLE_NAME = stringKey("aws.table.name"); + static final AttributeKey AWS_GUARDRAIL_ID = stringKey("aws.bedrock.guardrail.id"); + static final AttributeKey AWS_GUARDRAIL_ARN = stringKey("aws.bedrock.guardrail.arn"); @@ -2232,6 +2653,12 @@ index 0000000000..4aed4a58c0 + static final AttributeKey AWS_LAMBDA_RESOURCE_ID = + stringKey("aws.lambda.resource_mapping.id"); + ++ static final AttributeKey AWS_TABLE_ARN = stringKey("aws.table.arn"); ++ ++ static final AttributeKey AWS_AUTH_ACCESS_KEY = stringKey("aws.auth.account.access_key"); ++ ++ static final AttributeKey AWS_AUTH_REGION = stringKey("aws.auth.region"); ++ + static boolean isGenAiAttribute(String attributeKey) { + return attributeKey.equals(GEN_AI_REQUEST_MAX_TOKENS.getKey()) + || attributeKey.equals(GEN_AI_REQUEST_TEMPERATURE.getKey()) @@ -2322,10 +2749,10 @@ index 02d92ca070..aa98cd62c7 100644 BatchGetItem( DYNAMODB, diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRequestType.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRequestType.java -index 274ec27194..83d9353c3b 100644 +index 274ec27194..d8dba6cf5c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRequestType.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRequestType.java -@@ -5,7 +5,32 @@ +@@ -5,7 +5,34 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; @@ -2344,7 +2771,9 @@ index 274ec27194..83d9353c3b 100644 +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_SNS_TOPIC_ARN; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_STATE_MACHINE_ARN; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_STEP_FUNCTIONS_ACTIVITY_ARN; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_STREAM_ARN; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_STREAM_NAME; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_TABLE_ARN; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_TABLE_NAME; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.GEN_AI_MODEL; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.GEN_AI_REQUEST_MAX_TOKENS; @@ -2358,7 +2787,7 @@ index 274ec27194..83d9353c3b 100644 import io.opentelemetry.api.common.AttributeKey; import java.util.Collections; -@@ -13,16 +38,60 @@ import java.util.List; +@@ -13,16 +40,64 @@ import java.util.List; import java.util.Map; enum AwsSdkRequestType { @@ -2370,9 +2799,13 @@ index 274ec27194..83d9353c3b 100644 + + SQS(request(AWS_QUEUE_URL.getKey(), "QueueUrl"), request(AWS_QUEUE_NAME.getKey(), "QueueName")), + -+ KINESIS(request(AWS_STREAM_NAME.getKey(), "StreamName")), ++ KINESIS( ++ request(AWS_STREAM_NAME.getKey(), "StreamName"), ++ request(AWS_STREAM_ARN.getKey(), "StreamARN")), + -+ DYNAMODB(request(AWS_TABLE_NAME.getKey(), "TableName")), ++ DYNAMODB( ++ request(AWS_TABLE_NAME.getKey(), "TableName"), ++ response(AWS_TABLE_ARN.getKey(), "Table.TableArn")), + SNS( /* @@ -2962,19 +3395,37 @@ index 7ae1590152..5b7a188914 100644 + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingExecutionInterceptor.java -index 94243d0b11..7b15a1c84b 100644 +index 94243d0b11..06d8a9141b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingExecutionInterceptor.java -@@ -5,6 +5,8 @@ +@@ -5,6 +5,10 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_AUTH_ACCESS_KEY; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.AWS_AUTH_REGION; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsExperimentalAttributes.GEN_AI_SYSTEM; +import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsSdkRequestType.BEDROCKRUNTIME; import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsSdkRequestType.DYNAMODB; import io.opentelemetry.api.common.AttributeKey; -@@ -48,6 +50,7 @@ import software.amazon.awssdk.http.SdkHttpResponse; +@@ -28,6 +32,7 @@ import java.time.Instant; + import java.util.Optional; + import java.util.stream.Collectors; + import javax.annotation.Nullable; ++import software.amazon.awssdk.auth.credentials.AwsCredentials; + import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; + import software.amazon.awssdk.awscore.AwsResponse; + import software.amazon.awssdk.core.ClientType; +@@ -40,6 +45,7 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; + import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; + import software.amazon.awssdk.http.SdkHttpRequest; + import software.amazon.awssdk.http.SdkHttpResponse; ++import software.amazon.awssdk.regions.Region; + + /** + * AWS request execution interceptor. +@@ -48,6 +54,7 @@ import software.amazon.awssdk.http.SdkHttpResponse; * at any time. */ public final class TracingExecutionInterceptor implements ExecutionInterceptor { @@ -2982,7 +3433,34 @@ index 94243d0b11..7b15a1c84b 100644 // copied from DbIncubatingAttributes private static final AttributeKey DB_OPERATION = AttributeKey.stringKey("db.operation"); -@@ -342,6 +345,10 @@ public final class TracingExecutionInterceptor implements ExecutionInterceptor { +@@ -261,6 +268,26 @@ public final class TracingExecutionInterceptor implements ExecutionInterceptor { + SdkHttpRequest httpRequest = context.httpRequest(); + executionAttributes.putAttribute(SDK_HTTP_REQUEST_ATTRIBUTE, httpRequest); + ++ if (captureExperimentalSpanAttributes) { ++ AwsCredentials credentials = ++ executionAttributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS); ++ Region signingRegion = ++ executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION); ++ Span span = Span.fromContext(otelContext); ++ ++ if (credentials != null) { ++ String accessKeyId = credentials.accessKeyId(); ++ if (accessKeyId != null) { ++ span.setAttribute(AWS_AUTH_ACCESS_KEY, accessKeyId); ++ } ++ } ++ ++ if (signingRegion != null) { ++ String region = signingRegion.toString(); ++ span.setAttribute(AWS_AUTH_REGION, region); ++ } ++ } ++ + // We ought to pass the parent of otelContext here, but we didn't store it, and it shouldn't + // make a difference (unless we start supporting the http.resend_count attribute in this + // instrumentation, which, logically, we can't on this level of abstraction) +@@ -342,6 +369,10 @@ public final class TracingExecutionInterceptor implements ExecutionInterceptor { } } } @@ -3120,8 +3598,39 @@ index 08b000a05c..de0fe82638 100644 // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.13") +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy +index 9aaacb3abe..198990a509 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy +@@ -146,6 +146,8 @@ abstract class AbstractAws2ClientCoreTest extends InstrumentationSpecification { + "$RpcIncubatingAttributes.RPC_SYSTEM" "aws-api" + "$RpcIncubatingAttributes.RPC_SERVICE" "DynamoDb" + "$RpcIncubatingAttributes.RPC_METHOD" "CreateTable" ++ "aws.auth.account.access_key" "my-access-key" ++ "aws.auth.region" "ap-northeast-1" + "aws.agent" "java-aws-sdk" + "$AwsIncubatingAttributes.AWS_REQUEST_ID" "$requestId" + "aws.table.name" "sometable" +@@ -179,6 +181,8 @@ abstract class AbstractAws2ClientCoreTest extends InstrumentationSpecification { + "$RpcIncubatingAttributes.RPC_SYSTEM" "aws-api" + "$RpcIncubatingAttributes.RPC_SERVICE" "DynamoDb" + "$RpcIncubatingAttributes.RPC_METHOD" "Query" ++ "aws.auth.account.access_key" "my-access-key" ++ "aws.auth.region" "ap-northeast-1" + "aws.agent" "java-aws-sdk" + "$AwsIncubatingAttributes.AWS_REQUEST_ID" "$requestId" + "aws.table.name" "sometable" +@@ -211,6 +215,8 @@ abstract class AbstractAws2ClientCoreTest extends InstrumentationSpecification { + "$RpcIncubatingAttributes.RPC_SYSTEM" "aws-api" + "$RpcIncubatingAttributes.RPC_SERVICE" "$service" + "$RpcIncubatingAttributes.RPC_METHOD" "${operation}" ++ "aws.auth.account.access_key" "my-access-key" ++ "aws.auth.region" "ap-northeast-1" + "aws.agent" "java-aws-sdk" + "$AwsIncubatingAttributes.AWS_REQUEST_ID" "$requestId" + "aws.table.name" "sometable" diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy -index c571c0aa9c..1a4a00d95e 100644 +index c571c0aa9c..a6fbdab597 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -37,10 +37,19 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest @@ -3144,7 +3653,16 @@ index c571c0aa9c..1a4a00d95e 100644 import spock.lang.Unroll import java.nio.charset.StandardCharsets -@@ -148,8 +157,32 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { +@@ -134,6 +143,8 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { + "$RpcIncubatingAttributes.RPC_SYSTEM" "aws-api" + "$RpcIncubatingAttributes.RPC_SERVICE" "$service" + "$RpcIncubatingAttributes.RPC_METHOD" "${operation}" ++ "aws.auth.account.access_key" "my-access-key" ++ "aws.auth.region" "ap-northeast-1" + "aws.agent" "java-aws-sdk" + "$AwsIncubatingAttributes.AWS_REQUEST_ID" "$requestId" + if (service == "S3") { +@@ -148,8 +159,32 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { "$MessagingIncubatingAttributes.MESSAGING_SYSTEM" MessagingIncubatingAttributes.MessagingSystemIncubatingValues.AWS_SQS } else if (service == "Kinesis") { "aws.stream.name" "somestream" @@ -3179,7 +3697,7 @@ index c571c0aa9c..1a4a00d95e 100644 } } } -@@ -164,7 +197,7 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { +@@ -164,7 +199,7 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | s3ClientBuilder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" "S3" | "GetObject" | "GET" | "UNKNOWN" | s3ClientBuilder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" "Kinesis" | "DeleteStream" | "POST" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" @@ -3188,7 +3706,7 @@ index c571c0aa9c..1a4a00d95e 100644 567910cd-659e-55d4-8ccb-5aaf14679dc0 -@@ -174,15 +207,15 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { +@@ -174,15 +209,15 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { """ @@ -3211,7 +3729,7 @@ index c571c0aa9c..1a4a00d95e 100644 """ "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | { if (!Boolean.getBoolean("testLatestDeps")) { -@@ -244,170 +277,193 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { +@@ -244,170 +279,193 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 """ @@ -3566,6 +4084,101 @@ index c571c0aa9c..1a4a00d95e 100644 // TODO: Without AOP instrumentation of the HTTP client, we cannot model retries as // spans because of https://github.com/aws/aws-sdk-java-v2/issues/1741. We should at least tweak // the instrumentation to add Events for retries instead. +@@ -457,6 +515,8 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { + "$RpcIncubatingAttributes.RPC_SYSTEM" "aws-api" + "$RpcIncubatingAttributes.RPC_SERVICE" "S3" + "$RpcIncubatingAttributes.RPC_METHOD" "GetObject" ++ "aws.auth.account.access_key" "my-access-key" ++ "aws.auth.region" "ap-northeast-1" + "aws.agent" "java-aws-sdk" + "aws.bucket.name" "somebucket" + } +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientRecordHttpErrorTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientRecordHttpErrorTest.java +index 73d2a0ba82..f46361a078 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientRecordHttpErrorTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientRecordHttpErrorTest.java +@@ -172,6 +172,8 @@ public abstract class AbstractAws2ClientRecordHttpErrorTest { + span.hasKind(SpanKind.CLIENT); + span.hasNoParent(); + span.hasAttributesSatisfyingExactly( ++ equalTo(stringKey("aws.auth.account.access_key"), "my-access-key"), ++ equalTo(stringKey("aws.auth.region"), "ap-northeast-1"), + equalTo(SERVER_ADDRESS, "127.0.0.1"), + equalTo(SERVER_PORT, server.httpPort()), + equalTo(HTTP_REQUEST_METHOD, method), +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsBaseTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsBaseTest.java +index 902bfdc0d4..756968776e 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsBaseTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsBaseTest.java +@@ -214,6 +214,8 @@ public abstract class AbstractAws2SqsBaseTest { + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "Sqs"), + equalTo(RPC_METHOD, "CreateQueue"), ++ equalTo(stringKey("aws.auth.account.access_key"), "my-access-key"), ++ equalTo(stringKey("aws.auth.region"), "ap-northeast-1"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + satisfies(URL_FULL, v -> v.startsWith("http://localhost:" + sqsPort)), +@@ -257,6 +259,8 @@ public abstract class AbstractAws2SqsBaseTest { + equalTo(RPC_SYSTEM, "aws-api"), + equalTo(RPC_SERVICE, "Sqs"), + equalTo(RPC_METHOD, rcpMethod), ++ equalTo(stringKey("aws.auth.account.access_key"), "my-access-key"), ++ equalTo(stringKey("aws.auth.region"), "ap-northeast-1"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + satisfies(URL_FULL, v -> v.startsWith("http://localhost:" + sqsPort)), +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java +index 4d0a9be89c..382c035bf5 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java +@@ -84,6 +84,8 @@ public abstract class AbstractAws2SqsSuppressReceiveSpansTest extends AbstractAw + equalTo(RPC_METHOD, "ReceiveMessage"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), ++ equalTo(stringKey("aws.auth.account.access_key"), "my-access-key"), ++ equalTo(stringKey("aws.auth.region"), "ap-northeast-1"), + satisfies(URL_FULL, v -> v.startsWith("http://localhost:" + sqsPort)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, sqsPort)))); +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java +index 6fa897d462..f7ac28762c 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java +@@ -80,6 +80,9 @@ public abstract class AbstractAws2SqsTracingTest extends AbstractAws2SqsBaseTest + equalTo(RPC_METHOD, "SendMessage"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), ++ equalTo( ++ stringKey("aws.auth.account.access_key"), "my-access-key"), ++ equalTo(stringKey("aws.auth.region"), "ap-northeast-1"), + satisfies( + URL_FULL, v -> v.startsWith("http://localhost:" + sqsPort)), + equalTo(SERVER_ADDRESS, "localhost"), +@@ -133,6 +136,9 @@ public abstract class AbstractAws2SqsTracingTest extends AbstractAws2SqsBaseTest + equalTo(RPC_METHOD, "ReceiveMessage"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), ++ equalTo( ++ stringKey("aws.auth.account.access_key"), "my-access-key"), ++ equalTo(stringKey("aws.auth.region"), "ap-northeast-1"), + satisfies( + URL_FULL, v -> v.startsWith("http://localhost:" + sqsPort)), + equalTo(SERVER_ADDRESS, "localhost"), +diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java +index 8731717005..0d59b40f5e 100644 +--- a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java ++++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java +@@ -94,7 +94,8 @@ class AwsSpanAssertions { + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(RPC_SYSTEM, "aws-api"), + satisfies(RPC_METHOD, stringAssert -> stringAssert.isEqualTo(rpcMethod)), +- equalTo(RPC_SERVICE, "AmazonSQS"))); ++ equalTo(RPC_SERVICE, "AmazonSQS"), ++ equalTo(stringKey("aws.auth.account.access_key"), "x"))); + + if (spanName.endsWith("receive") + || spanName.endsWith("process") diff --git a/version.gradle.kts b/version.gradle.kts index a1cae43b4b..c1520e9947 100644 --- a/version.gradle.kts @@ -3573,8 +4186,8 @@ index a1cae43b4b..c1520e9947 100644 @@ -1,5 +1,5 @@ -val stableVersion = "2.11.0" -val alphaVersion = "2.11.0-alpha" -+val stableVersion = "2.11.0-adot2" -+val alphaVersion = "2.11.0-adot2-alpha" ++val stableVersion = "2.11.0-adot3" ++val alphaVersion = "2.11.0-adot3-alpha" allprojects { if (findProperty("otel.stable") != "true") { diff --git a/.github/workflows/application-signals-e2e-test.yml b/.github/workflows/application-signals-e2e-test.yml index d3ca04b77d..8d1bad5981 100644 --- a/.github/workflows/application-signals-e2e-test.yml +++ b/.github/workflows/application-signals-e2e-test.yml @@ -245,3 +245,19 @@ jobs: with: aws-region: us-east-1 caller-workflow-name: 'main-build' + + # This validation is to ensure that all test workflows relevant to this repo are actually + # being used in this repo, which is referring to all the other jobs in this file. + # + # If this starts failing, then it most likely means that new e2e test workflow was + # added to `aws-observability/aws-application-signals-test-framework`, but was not + # added to this file. It could also mean that a test in this file has been removed. + # + # If a particular test file is intended to not be tested in this repo and should not + # be failing this particular validation, then choose one of the following options: + # - Add the test file to the exclusions input (CSV format) to the workflow + # (see: https://github.com/aws-observability/aws-application-signals-test-framework/blob/main/.github/workflows/validate-e2e-tests-are-accounted-for.yml#L1) + # - Update the `validate-e2e-tests-are-accounted-for` job to change which "workflow files are expected to be used by this repo" + # (see: https://github.com/aws-observability/aws-application-signals-test-framework/blob/main/.github/workflows/validate-e2e-tests-are-accounted-for.yml) + validate-all-tests-are-accounted-for: + uses: aws-observability/aws-application-signals-test-framework/.github/workflows/validate-e2e-tests-are-accounted-for.yml@main diff --git a/.github/workflows/e2e-tests-app-with-java-agent.yml b/.github/workflows/e2e-tests-app-with-java-agent.yml index b2c4d744bf..d09283cb8f 100644 --- a/.github/workflows/e2e-tests-app-with-java-agent.yml +++ b/.github/workflows/e2e-tests-app-with-java-agent.yml @@ -167,18 +167,18 @@ jobs: VALIDATOR_COMMAND: -c spark-otel-trace-metric-validation.yml --endpoint http://app:4567 --metric-namespace aws-otel-integ-test -t ${{ github.run_id }}-${{ github.run_number }} # publish status - publish-build-status: - needs: [ test_Spring_App_With_Java_Agent, test_Spark_App_With_Java_Agent, test_Spark_AWS_SDK_V1_App_With_Java_Agent ] - if: ${{ always() }} - uses: ./.github/workflows/publish-status.yml - with: - namespace: 'ADOT/GitHubActions' - repository: ${{ github.repository }} - branch: ${{ github.ref_name }} - workflow: ${{ inputs.caller-workflow-name }} - success: ${{ needs.test_Spring_App_With_Java_Agent.result == 'success' && - needs.test_Spark_App_With_Java_Agent.result == 'success' && - needs.test_Spark_AWS_SDK_V1_App_With_Java_Agent.result == 'success' }} - region: us-east-1 - secrets: - roleArn: ${{ secrets.METRICS_ROLE_ARN }} + # publish-build-status: + # needs: [ test_Spring_App_With_Java_Agent, test_Spark_App_With_Java_Agent, test_Spark_AWS_SDK_V1_App_With_Java_Agent ] + # if: ${{ always() }} + # uses: ./.github/workflows/publish-status.yml + # with: + # namespace: 'ADOT/GitHubActions' + # repository: ${{ github.repository }} + # branch: ${{ github.ref_name }} + # workflow: ${{ inputs.caller-workflow-name }} + # success: ${{ needs.test_Spring_App_With_Java_Agent.result == 'success' && + # needs.test_Spark_App_With_Java_Agent.result == 'success' && + # needs.test_Spark_AWS_SDK_V1_App_With_Java_Agent.result == 'success' }} + # region: us-east-1 + # secrets: + # roleArn: ${{ secrets.METRICS_ROLE_ARN }} diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 2255a97aca..30c74f3c87 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -263,16 +263,21 @@ jobs: adot-image-name: ${{ needs.build.outputs.staging-image }} publish-build-status: - needs: [ build, contract-tests ] - if: ${{ always() }} - uses: ./.github/workflows/publish-status.yml - with: - namespace: 'ADOT/GitHubActions' - repository: ${{ github.repository }} - branch: ${{ github.ref_name }} - workflow: main-build - success: ${{ needs.build.result == 'success' && - needs.contract-tests.result == 'success' }} - region: us-east-1 - secrets: - roleArn: ${{ secrets.METRICS_ROLE_ARN }} + name: "Publish Main Build Status" + needs: [ build, e2e-test, contract-tests, application-signals-lambda-layer-build, application-signals-e2e-test ] + runs-on: ubuntu-latest + if: always() + steps: + - name: Configure AWS Credentials for emitting metrics + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.METRICS_ROLE_ARN }} + aws-region: us-east-1 + + - name: Publish main build status + run: | + value="${{ needs.build.result == 'success' && needs.e2e-test.result == 'success' && needs.contract-tests.result == 'success' && needs.application-signals-lambda-layer-build.result == 'success' && needs.application-signals-e2e-test.result == 'success' && '0.0' || '1.0' }}" + aws cloudwatch put-metric-data --namespace 'ADOT/GitHubActions' \ + --metric-name Failure \ + --dimensions repository=${{ github.repository }},branch=${{ github.ref_name }},workflow=main_build \ + --value $value diff --git a/.github/workflows/owasp.yml b/.github/workflows/owasp.yml index ecf34587f2..54ce812326 100644 --- a/.github/workflows/owasp.yml +++ b/.github/workflows/owasp.yml @@ -97,7 +97,7 @@ jobs: id: high_scan_v2 uses: ./.github/actions/image_scan with: - image-ref: "public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v2.11.0" + image-ref: "public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v2.11.1" severity: 'CRITICAL,HIGH' - name: Perform low image scan on v2 @@ -105,7 +105,7 @@ jobs: id: low_scan_v2 uses: ./.github/actions/image_scan with: - image-ref: "public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v2.11.0" + image-ref: "public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v2.11.1" severity: 'MEDIUM,LOW,UNKNOWN' - name: Configure AWS Credentials for emitting metrics diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 8696c3d915..ce9d29ddc8 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -21,6 +21,7 @@ permissions: jobs: build: + environment: Release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-lambda.yml b/.github/workflows/release-lambda.yml index 87336ab4b9..2c1b2f037c 100644 --- a/.github/workflows/release-lambda.yml +++ b/.github/workflows/release-lambda.yml @@ -9,10 +9,10 @@ on: aws_region: description: 'Deploy to aws regions' required: true - default: 'us-east-1, us-east-2, us-west-1, us-west-2, ap-south-1, ap-northeast-3, ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-northeast-1, ca-central-1, eu-central-1, eu-west-1, eu-west-2, eu-west-3, eu-north-1, sa-east-1, af-south-1, ap-east-1, ap-south-2, ap-southeast-3, ap-southeast-4, eu-central-2, eu-south-1, eu-south-2, il-central-1, me-central-1, me-south-1' + default: 'us-east-1, us-east-2, us-west-1, us-west-2, ap-south-1, ap-northeast-3, ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-northeast-1, ca-central-1, eu-central-1, eu-west-1, eu-west-2, eu-west-3, eu-north-1, sa-east-1, af-south-1, ap-east-1, ap-south-2, ap-southeast-3, ap-southeast-4, eu-central-2, eu-south-1, eu-south-2, il-central-1, me-central-1, me-south-1, ap-southeast-5, ap-southeast-7, mx-central-1, ca-west-1, cn-north-1, cn-northwest-1' env: - COMMERCIAL_REGIONS: us-east-1, us-east-2, us-west-1, us-west-2, ap-south-1, ap-northeast-3, ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-northeast-1, ca-central-1, eu-central-1, eu-west-1, eu-west-2, eu-west-3, eu-north-1, sa-east-1 + COMMERCIAL_REGIONS: us-east-1, us-east-2, us-west-1, us-west-2, ap-south-1, ap-northeast-3, ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-northeast-1, ca-central-1, eu-central-1, eu-west-1, eu-west-2, eu-west-3, eu-north-1, sa-east-1, ap-southeast-5, ap-southeast-7, mx-central-1, ca-west-1, cn-north-1, cn-northwest-1 LAYER_NAME: AWSOpenTelemetryDistroJava permissions: @@ -21,6 +21,7 @@ permissions: jobs: build-layer: + environment: Release runs-on: ubuntu-latest outputs: aws_regions_json: ${{ steps.set-matrix.outputs.aws_regions_json }} diff --git a/.github/workflows/release-udp-exporter.yml b/.github/workflows/release-udp-exporter.yml index 50b4d67065..e200a7c3a9 100644 --- a/.github/workflows/release-udp-exporter.yml +++ b/.github/workflows/release-udp-exporter.yml @@ -21,6 +21,7 @@ jobs: id-token: write release-udp-exporter: + environment: Release runs-on: ubuntu-latest needs: validate-udp-exporter-e2e-test steps: diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java index 08036c7d9b..5af9cae391 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java @@ -54,6 +54,7 @@ public abstract class AwsSdkBaseTest extends ContractTestBase { "localstack", "s3.localstack", "create-bucket.s3.localstack", + "cross-account-bucket.s3.localstack", "put-object.s3.localstack", "get-object.s3.localstack"); @@ -258,6 +259,12 @@ private ThrowingConsumer assertKeyIsPresent(String key) { }; } + private ThrowingConsumer assertKeyIsNotPresent(String key) { + return (attribute) -> { + assertThat(attribute.getKey()).isNotEqualTo(key); + }; + } + /** All the spans of the AWS SDK Should have a RPC properties. */ private void assertSemanticConventionsAttributes( List attributesList, @@ -308,6 +315,9 @@ private void assertSpanClientAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, String address, int port, String url, @@ -327,6 +337,9 @@ private void assertSpanClientAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, address, port, url, @@ -345,6 +358,9 @@ private void assertSpanProducerAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, String address, int port, String url, @@ -363,6 +379,9 @@ private void assertSpanProducerAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, address, port, url, @@ -412,6 +431,9 @@ private void assertSpanAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, String address, int port, String url, @@ -436,6 +458,9 @@ private void assertSpanAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, awsSpanKind); for (var assertion : extraAssertions) { assertThat(spanAttributes).satisfiesOnlyOnce(assertion); @@ -452,6 +477,9 @@ private void assertAwsAttributes( String type, String identifier, String clouformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, String spanKind) { var assertions = @@ -471,6 +499,27 @@ private void assertAwsAttributes( assertAttribute( AppSignalsConstants.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, clouformationIdentifier)); } + + if (remoteAccountId != null) { + assertions.satisfiesOnlyOnce( + assertKeyIsPresent(AppSignalsConstants.AWS_REMOTE_RESOURCE_IDENTIFIER)); + assertions.satisfiesOnlyOnce( + assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_ACCOUNT_ID, remoteAccountId)); + assertKeyIsNotPresent(AppSignalsConstants.AWS_REMOTE_RESOURCE_ACCESS_KEY); + } + + if (remoteAccessKey != null) { + assertions.satisfiesOnlyOnce( + assertKeyIsPresent(AppSignalsConstants.AWS_REMOTE_RESOURCE_IDENTIFIER)); + assertions.satisfiesOnlyOnce( + assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_ACCESS_KEY, remoteAccessKey)); + assertKeyIsNotPresent(AppSignalsConstants.AWS_REMOTE_RESOURCE_ACCOUNT_ID); + } + + if (remoteRegion != null) { + assertions.satisfiesOnlyOnce( + assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_REGION, remoteRegion)); + } } private void assertSqsConsumerAwsAttributes(List attributesList, String operation) { @@ -495,6 +544,9 @@ protected void assertMetricClientAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -507,6 +559,9 @@ protected void assertMetricClientAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, expectedSum); } @@ -520,6 +575,9 @@ protected void assertMetricProducerAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -532,6 +590,9 @@ protected void assertMetricProducerAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, expectedSum); } @@ -545,6 +606,9 @@ protected void assertMetricConsumerAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -557,6 +621,9 @@ protected void assertMetricConsumerAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, expectedSum); } @@ -571,6 +638,9 @@ protected void assertMetricAttributes( String type, String identifier, String cloudformationIdentifier, + String remoteAccountId, + String remoteAccessKey, + String remoteRegion, Double expectedSum) { assertThat(resourceScopeMetrics) .anySatisfy( @@ -591,6 +661,9 @@ protected void assertMetricAttributes( type, identifier, cloudformationIdentifier, + remoteAccountId, + remoteAccessKey, + remoteRegion, spanKind); if (expectedSum != null) { double actualSum = dataPoint.getSum(); @@ -634,6 +707,9 @@ protected void doTestS3CreateBucket() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "create-bucket.s3.localstack", 4566, "http://create-bucket.s3.localstack:4566", @@ -649,6 +725,9 @@ protected void doTestS3CreateBucket() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -660,6 +739,9 @@ protected void doTestS3CreateBucket() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -671,6 +753,9 @@ protected void doTestS3CreateBucket() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -702,6 +787,9 @@ protected void doTestS3CreateObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "put-object.s3.localstack", 4566, "http://put-object.s3.localstack:4566", @@ -717,6 +805,9 @@ protected void doTestS3CreateObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -728,6 +819,9 @@ protected void doTestS3CreateObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -739,6 +833,9 @@ protected void doTestS3CreateObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -769,6 +866,9 @@ protected void doTestS3GetObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "get-object.s3.localstack", 4566, "http://get-object.s3.localstack:4566", @@ -784,6 +884,9 @@ protected void doTestS3GetObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -795,6 +898,9 @@ protected void doTestS3GetObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -806,6 +912,9 @@ protected void doTestS3GetObject() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -836,6 +945,9 @@ protected void doTestS3Error() { type, identifier, cloudformationIdentifier, + null, + null, + null, "error-bucket.s3.test", 8080, "http://error-bucket.s3.test:8080", @@ -851,6 +963,9 @@ protected void doTestS3Error() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -862,6 +977,9 @@ protected void doTestS3Error() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -873,6 +991,9 @@ protected void doTestS3Error() { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); } @@ -903,6 +1024,9 @@ protected void doTestS3Fault() { type, identifier, cloudformationIdentifier, + null, + null, + null, "fault-bucket.s3.test", 8080, "http://fault-bucket.s3.test:8080", @@ -918,6 +1042,9 @@ protected void doTestS3Fault() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -929,6 +1056,9 @@ protected void doTestS3Fault() { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); assertMetricClientAttributes( metrics, @@ -940,6 +1070,9 @@ protected void doTestS3Fault() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -978,6 +1111,9 @@ protected void doTestDynamoDbCreateTable() { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -993,6 +1129,9 @@ protected void doTestDynamoDbCreateTable() { type, identifier, cloudformationIdentifier, + null, + null, + null, 20000.0); assertMetricClientAttributes( metrics, @@ -1004,6 +1143,9 @@ protected void doTestDynamoDbCreateTable() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1015,6 +1157,9 @@ protected void doTestDynamoDbCreateTable() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1045,6 +1190,9 @@ protected void doTestDynamoDbPutItem() { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -1060,6 +1208,9 @@ protected void doTestDynamoDbPutItem() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1071,6 +1222,9 @@ protected void doTestDynamoDbPutItem() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1082,6 +1236,91 @@ protected void doTestDynamoDbPutItem() { type, identifier, cloudformationIdentifier, + null, + null, + null, + 0.0); + } + + protected void doTestDynamoDbDescribeTable() { + appClient.get("/ddb/describetable/test-table").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /ddb/describetable/:tablename"; + var type = "AWS::DynamoDB::Table"; + var identifier = "test-table"; + var cloudformationIdentifier = "test-table"; + var remoteAccountId = "000000000000"; + var remoteRegion = "us-west-2"; + + assertSpanClientAttributes( + traces, + dynamoDbSpanName("DescribeTable"), + getDynamoDbRpcServiceName(), + localService, + localOperation, + getDynamoDbServiceName(), + "DescribeTable", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + "localstack", + 4566, + "http://localstack:4566", + 200, + dynamoDbAttributes("DescribeTable", "test-table")); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getDynamoDbServiceName(), + "DescribeTable", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getDynamoDbServiceName(), + "DescribeTable", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getDynamoDbServiceName(), + "DescribeTable", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, 0.0); } @@ -1112,6 +1351,9 @@ protected void doTestDynamoDbError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "error.test", 8080, "http://error.test:8080", @@ -1127,6 +1369,9 @@ protected void doTestDynamoDbError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1138,6 +1383,9 @@ protected void doTestDynamoDbError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1149,6 +1397,9 @@ protected void doTestDynamoDbError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); } @@ -1185,6 +1436,9 @@ protected void doTestDynamoDbFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "fault.test", 8080, "http://fault.test:8080", @@ -1200,6 +1454,9 @@ protected void doTestDynamoDbFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 20000.0); assertMetricClientAttributes( metrics, @@ -1211,6 +1468,9 @@ protected void doTestDynamoDbFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); assertMetricClientAttributes( metrics, @@ -1222,6 +1482,9 @@ protected void doTestDynamoDbFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1252,6 +1515,9 @@ protected void doTestSQSCreateQueue() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -1267,6 +1533,9 @@ protected void doTestSQSCreateQueue() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1278,6 +1547,9 @@ protected void doTestSQSCreateQueue() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1289,6 +1561,9 @@ protected void doTestSQSCreateQueue() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1320,6 +1595,9 @@ protected void doTestSQSSendMessage() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -1336,6 +1614,9 @@ protected void doTestSQSSendMessage() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricProducerAttributes( metrics, @@ -1347,6 +1628,9 @@ protected void doTestSQSSendMessage() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricProducerAttributes( metrics, @@ -1358,6 +1642,9 @@ protected void doTestSQSSendMessage() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1406,6 +1693,9 @@ protected void doTestSQSReceiveMessage() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricConsumerAttributes( metrics, @@ -1417,6 +1707,9 @@ protected void doTestSQSReceiveMessage() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1448,6 +1741,9 @@ protected void doTestSQSError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "error.test", 8080, "http://error.test:8080", @@ -1464,6 +1760,9 @@ protected void doTestSQSError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricProducerAttributes( metrics, @@ -1475,6 +1774,9 @@ protected void doTestSQSError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricProducerAttributes( metrics, @@ -1486,6 +1788,9 @@ protected void doTestSQSError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); } @@ -1517,6 +1822,9 @@ protected void doTestSQSFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "fault.test", 8080, "http://fault.test:8080", @@ -1533,6 +1841,9 @@ protected void doTestSQSFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricProducerAttributes( metrics, @@ -1544,6 +1855,9 @@ protected void doTestSQSFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); assertMetricProducerAttributes( metrics, @@ -1555,6 +1869,9 @@ protected void doTestSQSFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1585,6 +1902,9 @@ protected void doTestKinesisPutRecord() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -1600,6 +1920,9 @@ protected void doTestKinesisPutRecord() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1611,6 +1934,9 @@ protected void doTestKinesisPutRecord() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1622,11 +1948,14 @@ protected void doTestKinesisPutRecord() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } - protected void doTestKinesisError() throws Exception { - appClient.get("/kinesis/error").aggregate().join(); + protected void doTestKinesisDescribeStream() { + appClient.get("/kinesis/describestream/test-stream").aggregate().join(); var traces = mockCollectorClient.getTraces(); var metrics = mockCollectorClient.getMetrics( @@ -1636,14 +1965,98 @@ protected void doTestKinesisError() throws Exception { AppSignalsConstants.LATENCY_METRIC)); var localService = getApplicationOtelServiceName(); - var localOperation = "GET /kinesis/error"; + var localOperation = "GET /kinesis/describestream/:streamname"; var type = "AWS::Kinesis::Stream"; - var identifier = "nonexistantstream"; - var cloudformationIdentifier = "nonexistantstream"; + var identifier = "test-stream"; + var cloudformationIdentifier = "test-stream"; + var remoteAccountId = "000000000000"; + var remoteRegion = "us-west-2"; assertSpanClientAttributes( traces, - kinesisSpanName("PutRecord"), + kinesisSpanName("DescribeStream"), + getKinesisRpcServiceName(), + localService, + localOperation, + getKinesisServiceName(), + "DescribeStream", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_STREAM_ARN, + "arn:aws:kinesis:us-west-2:000000000000:stream/test-stream"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getKinesisServiceName(), + "DescribeStream", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getKinesisServiceName(), + "DescribeStream", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getKinesisServiceName(), + "DescribeStream", + type, + identifier, + cloudformationIdentifier, + remoteAccountId, + null, + remoteRegion, + 0.0); + } + + protected void doTestKinesisError() throws Exception { + appClient.get("/kinesis/error").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /kinesis/error"; + var type = "AWS::Kinesis::Stream"; + var identifier = "nonexistantstream"; + var cloudformationIdentifier = "nonexistantstream"; + + assertSpanClientAttributes( + traces, + kinesisSpanName("PutRecord"), getKinesisRpcServiceName(), localService, localOperation, @@ -1652,6 +2065,9 @@ protected void doTestKinesisError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "error.test", 8080, "http://error.test:8080", @@ -1668,6 +2084,9 @@ protected void doTestKinesisError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1679,6 +2098,9 @@ protected void doTestKinesisError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1690,6 +2112,9 @@ protected void doTestKinesisError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); } @@ -1720,6 +2145,9 @@ protected void doTestKinesisFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "fault.test", 8080, "http://fault.test:8080", @@ -1735,6 +2163,9 @@ protected void doTestKinesisFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1746,6 +2177,9 @@ protected void doTestKinesisFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); assertMetricClientAttributes( metrics, @@ -1757,6 +2191,9 @@ protected void doTestKinesisFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1787,6 +2224,9 @@ protected void doTestBedrockAgentKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1804,6 +2244,9 @@ protected void doTestBedrockAgentKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1815,6 +2258,9 @@ protected void doTestBedrockAgentKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1826,6 +2272,9 @@ protected void doTestBedrockAgentKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1855,6 +2304,9 @@ protected void doTestBedrockAgentAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1870,6 +2322,9 @@ protected void doTestBedrockAgentAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1881,6 +2336,9 @@ protected void doTestBedrockAgentAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1892,6 +2350,9 @@ protected void doTestBedrockAgentAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1921,6 +2382,9 @@ protected void doTestBedrockAgentDataSourceId() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1938,6 +2402,9 @@ protected void doTestBedrockAgentDataSourceId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -1949,6 +2416,9 @@ protected void doTestBedrockAgentDataSourceId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -1960,6 +2430,9 @@ protected void doTestBedrockAgentDataSourceId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -1989,6 +2462,9 @@ protected void doTestBedrockRuntimeAi21Jamba() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2012,6 +2488,9 @@ protected void doTestBedrockRuntimeAi21Jamba() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2023,6 +2502,9 @@ protected void doTestBedrockRuntimeAi21Jamba() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2034,6 +2516,9 @@ protected void doTestBedrockRuntimeAi21Jamba() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2063,6 +2548,9 @@ protected void doTestBedrockRuntimeAmazonTitan() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2089,6 +2577,9 @@ protected void doTestBedrockRuntimeAmazonTitan() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2100,6 +2591,9 @@ protected void doTestBedrockRuntimeAmazonTitan() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2111,6 +2605,9 @@ protected void doTestBedrockRuntimeAmazonTitan() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2142,6 +2639,9 @@ protected void doTestBedrockRuntimeAnthropicClaude() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2168,6 +2668,9 @@ protected void doTestBedrockRuntimeAnthropicClaude() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2179,6 +2682,9 @@ protected void doTestBedrockRuntimeAnthropicClaude() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2190,6 +2696,9 @@ protected void doTestBedrockRuntimeAnthropicClaude() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2221,6 +2730,9 @@ protected void doTestBedrockRuntimeCohereCommandR() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2246,6 +2758,9 @@ protected void doTestBedrockRuntimeCohereCommandR() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2257,6 +2772,9 @@ protected void doTestBedrockRuntimeCohereCommandR() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2268,6 +2786,9 @@ protected void doTestBedrockRuntimeCohereCommandR() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2299,6 +2820,9 @@ protected void doTestBedrockRuntimeMetaLlama() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2323,6 +2847,9 @@ protected void doTestBedrockRuntimeMetaLlama() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2334,6 +2861,9 @@ protected void doTestBedrockRuntimeMetaLlama() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2345,6 +2875,9 @@ protected void doTestBedrockRuntimeMetaLlama() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2376,6 +2909,9 @@ protected void doTestBedrockRuntimeMistral() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2401,6 +2937,9 @@ protected void doTestBedrockRuntimeMistral() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2412,6 +2951,9 @@ protected void doTestBedrockRuntimeMistral() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2423,6 +2965,9 @@ protected void doTestBedrockRuntimeMistral() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2453,6 +2998,9 @@ protected void doTestBedrockGuardrailId() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2473,6 +3021,9 @@ protected void doTestBedrockGuardrailId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2484,6 +3035,9 @@ protected void doTestBedrockGuardrailId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2495,6 +3049,9 @@ protected void doTestBedrockGuardrailId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2524,6 +3081,9 @@ protected void doTestBedrockAgentRuntimeAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2539,6 +3099,9 @@ protected void doTestBedrockAgentRuntimeAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2550,6 +3113,9 @@ protected void doTestBedrockAgentRuntimeAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2561,6 +3127,9 @@ protected void doTestBedrockAgentRuntimeAgentId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2591,6 +3160,9 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2608,6 +3180,9 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2619,6 +3194,9 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2630,6 +3208,9 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2659,6 +3240,9 @@ protected void doTestSecretsManagerDescribeSecret() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -2677,6 +3261,9 @@ protected void doTestSecretsManagerDescribeSecret() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2688,6 +3275,9 @@ protected void doTestSecretsManagerDescribeSecret() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2699,6 +3289,9 @@ protected void doTestSecretsManagerDescribeSecret() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2724,6 +3317,9 @@ protected void doTestSecretsManagerError() throws Exception { null, null, null, + null, + null, + null, "error.test", 8080, "http://error.test:8080", @@ -2739,6 +3335,9 @@ protected void doTestSecretsManagerError() throws Exception { null, null, null, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2750,6 +3349,9 @@ protected void doTestSecretsManagerError() throws Exception { null, null, null, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2761,6 +3363,9 @@ protected void doTestSecretsManagerError() throws Exception { null, null, null, + null, + null, + null, 1.0); } @@ -2787,6 +3392,9 @@ protected void doTestSecretsManagerFault() throws Exception { null, null, null, + null, + null, + null, "fault.test", 8080, "http://fault.test:8080", @@ -2802,6 +3410,9 @@ protected void doTestSecretsManagerFault() throws Exception { null, null, null, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2813,6 +3424,9 @@ protected void doTestSecretsManagerFault() throws Exception { null, null, null, + null, + null, + null, 1.0); assertMetricClientAttributes( metrics, @@ -2824,6 +3438,9 @@ protected void doTestSecretsManagerFault() throws Exception { null, null, null, + null, + null, + null, 0.0); } @@ -2854,6 +3471,9 @@ protected void doTestStepFunctionsDescribeStateMachine() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -2872,6 +3492,9 @@ protected void doTestStepFunctionsDescribeStateMachine() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2883,6 +3506,9 @@ protected void doTestStepFunctionsDescribeStateMachine() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2894,6 +3520,9 @@ protected void doTestStepFunctionsDescribeStateMachine() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2923,6 +3552,9 @@ protected void doTestStepFunctionsDescribeActivity() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -2941,6 +3573,9 @@ protected void doTestStepFunctionsDescribeActivity() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -2952,6 +3587,9 @@ protected void doTestStepFunctionsDescribeActivity() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -2963,6 +3601,9 @@ protected void doTestStepFunctionsDescribeActivity() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -2994,6 +3635,9 @@ protected void doTestStepFunctionsError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "error.test", 8080, "http://error.test:8080", @@ -3012,6 +3656,9 @@ protected void doTestStepFunctionsError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -3023,6 +3670,9 @@ protected void doTestStepFunctionsError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); assertMetricClientAttributes( metrics, @@ -3034,6 +3684,9 @@ protected void doTestStepFunctionsError() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); } @@ -3064,6 +3717,9 @@ protected void doTestStepFunctionsFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "fault.test", 8080, "http://fault.test:8080", @@ -3082,6 +3738,9 @@ protected void doTestStepFunctionsFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 5000.0); assertMetricClientAttributes( metrics, @@ -3093,6 +3752,9 @@ protected void doTestStepFunctionsFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 1.0); assertMetricClientAttributes( metrics, @@ -3104,6 +3766,9 @@ protected void doTestStepFunctionsFault() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, 0.0); } @@ -3134,6 +3799,9 @@ protected void doTestSnsGetTopicAttributes() throws Exception { type, identifier, cloudformationIdentifier, + null, + null, + null, "localstack", 4566, "http://localstack:4566", @@ -3167,6 +3835,9 @@ protected void doTestSnsError() throws Exception { null, null, null, + null, + null, + null, "error.test", 8080, "http://error.test:8080", @@ -3183,6 +3854,9 @@ protected void doTestSnsError() throws Exception { null, null, null, + null, + null, + null, 5000.0); assertMetricClientAttributes( @@ -3195,6 +3869,9 @@ protected void doTestSnsError() throws Exception { null, null, null, + null, + null, + null, 0.0); assertMetricClientAttributes( @@ -3207,6 +3884,9 @@ protected void doTestSnsError() throws Exception { null, null, null, + null, + null, + null, 1.0); } @@ -3233,6 +3913,9 @@ protected void doTestSnsFault() throws Exception { null, null, null, + null, + null, + null, "fault.test", 8080, "http://fault.test:8080", @@ -3249,6 +3932,9 @@ protected void doTestSnsFault() throws Exception { null, null, null, + null, + null, + null, 5000.0); assertMetricClientAttributes( @@ -3261,6 +3947,9 @@ protected void doTestSnsFault() throws Exception { null, null, null, + null, + null, + null, 1.0); assertMetricClientAttributes( @@ -3273,6 +3962,91 @@ protected void doTestSnsFault() throws Exception { null, null, null, + null, + null, + null, + 0.0); + } + + protected void doTestCrossAccount(String remoteRegion) throws Exception { + appClient.get("/crossaccount/createbucket/accountb").aggregate().join(); + + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /crossaccount/createbucket/accountb"; + var type = "AWS::S3::Bucket"; + var identifier = "cross-account-bucket"; + var cloudformationIdentifier = "cross-account-bucket"; + var remoteAccessKey = "account_b_access_key_id"; + + assertSpanClientAttributes( + traces, + s3SpanName("CreateBucket"), + getS3RpcServiceName(), + localService, + localOperation, + getS3ServiceName(), + "CreateBucket", + type, + identifier, + cloudformationIdentifier, + null, + remoteAccessKey, + remoteRegion, + "cross-account-bucket.s3.localstack", + 4566, + "http://cross-account-bucket.s3.localstack:4566", + 200, + List.of( + assertAttribute(SemanticConventionsConstants.AWS_BUCKET_NAME, "cross-account-bucket"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getS3ServiceName(), + "CreateBucket", + type, + identifier, + cloudformationIdentifier, + null, + remoteAccessKey, + remoteRegion, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getS3ServiceName(), + "CreateBucket", + type, + identifier, + cloudformationIdentifier, + null, + remoteAccessKey, + remoteRegion, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getS3ServiceName(), + "CreateBucket", + type, + identifier, + cloudformationIdentifier, + null, + remoteAccessKey, + remoteRegion, 0.0); } } diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java index 1395bc3ac8..6389fdcf5a 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java @@ -181,6 +181,11 @@ void testDynamoDbPutItem() { doTestDynamoDbPutItem(); } + @Test + void testDynamoDbDescribeTable() { + doTestDynamoDbDescribeTable(); + } + @Test void testDynamoDbError() throws Exception { doTestDynamoDbError(); @@ -221,6 +226,11 @@ void testKinesisPutRecord() throws Exception { doTestKinesisPutRecord(); } + @Test + void testKinesisDescribeStream() { + doTestKinesisDescribeStream(); + } + @Test void testKinsesisError() throws Exception { doTestKinesisError(); @@ -340,4 +350,9 @@ void testSnsError() throws Exception { void testSnsFault() throws Exception { doTestStepFunctionsFault(); } + + @Test + void testCrossAccount() throws Exception { + doTestCrossAccount(null); + } } diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java index 744a3be251..536c1e6b2a 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java @@ -179,6 +179,11 @@ void testDynamoDbPutItem() { doTestDynamoDbPutItem(); } + @Test + void testDynamoDbDescribeTable() { + doTestDynamoDbDescribeTable(); + } + @Test void testDynamoDbError() throws Exception { doTestDynamoDbError(); @@ -224,6 +229,11 @@ void testKinesisPutRecord() throws Exception { doTestKinesisPutRecord(); } + @Test + void testKinesisDescribeStream() { + doTestKinesisDescribeStream(); + } + @Test void testKinesisError() throws Exception { doTestKinesisError(); @@ -343,4 +353,9 @@ void testSnsError() throws Exception { void testSnsFault() throws Exception { doTestStepFunctionsFault(); } + + @Test + void testCrossAccount() throws Exception { + doTestCrossAccount("eu-central-1"); + } } diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java index 0ff11305c2..d4bac11941 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java @@ -35,7 +35,10 @@ public class AppSignalsConstants { "aws.remote.resource.cfn.primary.identifier"; public static final String AWS_SPAN_KIND = "aws.span.kind"; public static final String AWS_REMOTE_DB_USER = "aws.remote.db.user"; - + public static final String AWS_REMOTE_RESOURCE_ACCESS_KEY = + "aws.remote.resource.account.access_key"; + public static final String AWS_REMOTE_RESOURCE_ACCOUNT_ID = "aws.remote.resource.account.id"; + public static final String AWS_REMOTE_RESOURCE_REGION = "aws.remote.resource.region"; // JVM Metrics public static final String JVM_GC_DURATION = "jvm.gc.collections.elapsed"; public static final String JVM_GC_COUNT = "jvm.gc.collections.count"; diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java index dee50bb8ea..12c116d3c0 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java @@ -56,6 +56,7 @@ public class SemanticConventionsConstants { public static final String AWS_TABLE_NAME = "aws.table.name"; public static final String AWS_QUEUE_URL = "aws.queue.url"; public static final String AWS_QUEUE_NAME = "aws.queue.name"; + public static final String AWS_STREAM_ARN = "aws.stream.arn"; public static final String AWS_STREAM_NAME = "aws.stream.name"; public static final String AWS_KNOWLEDGE_BASE_ID = "aws.bedrock.knowledge_base.id"; public static final String AWS_DATA_SOURCE_ID = "aws.bedrock.data_source.id"; diff --git a/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java b/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java index 6b39559b0d..7f8f331d66 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java @@ -21,8 +21,11 @@ import static spark.Spark.port; import static spark.Spark.post; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; import com.amazonaws.regions.Regions; import com.amazonaws.services.bedrock.AmazonBedrockClient; @@ -49,6 +52,7 @@ import com.amazonaws.services.identitymanagement.model.PutRolePolicyRequest; import com.amazonaws.services.kinesis.AmazonKinesisClient; import com.amazonaws.services.kinesis.model.CreateStreamRequest; +import com.amazonaws.services.kinesis.model.DescribeStreamRequest; import com.amazonaws.services.kinesis.model.PutRecordRequest; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.CreateBucketRequest; @@ -152,6 +156,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupStepFunctions(); setupSns(); setupBedrock(); + setupCrossAccount(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); @@ -320,6 +325,33 @@ private static void setupDynamoDb() { dynamoDbClient.putItem(putItemRequest); return ""; }); + + get( + "/ddb/describetable/:tablename", + (req, res) -> { + var tableName = req.params(":tablename"); + + var createTableRequest = + new CreateTableRequest() + .withTableName(tableName) + .withAttributeDefinitions( + new AttributeDefinition() + .withAttributeName("partitionKey") + .withAttributeType("S")) + .withKeySchema( + new KeySchemaElement() + .withAttributeName("partitionKey") + .withKeyType(KeyType.HASH)) + .withProvisionedThroughput( + new ProvisionedThroughput() + .withReadCapacityUnits(1L) + .withWriteCapacityUnits(1L)); + dynamoDbClient.createTable(createTableRequest); + + dynamoDbClient.describeTable(tableName); + return ""; + }); + get( "/ddb/error", (req, res) -> { @@ -405,6 +437,30 @@ private static void setupKinesis() { return ""; }); + get( + "/kinesis/describestream/:streamname", + (req, res) -> { + var streamName = req.params(":streamname"); + + var kinesisClient = + AmazonKinesisClient.builder() + .withEndpointConfiguration(endpointConfiguration) + .withCredentials(CREDENTIALS_PROVIDER) + .build(); + + var createStreamRequest = new CreateStreamRequest(); + createStreamRequest.setStreamName(streamName); + + kinesisClient.createStream(createStreamRequest); + + // Describe stream using ARN + var streamArn = "arn:aws:kinesis:us-west-2:000000000000:stream/" + streamName; + DescribeStreamRequest describeStreamRequest = + new DescribeStreamRequest().withStreamARN(streamArn); + kinesisClient.describeStream(describeStreamRequest); + return ""; + }); + get( "/kinesis/error", (req, res) -> { @@ -1191,4 +1247,30 @@ private static void setupBedrock() { return ""; }); } + + private static void setupCrossAccount() { + // Create credentials provider with temporary credentials + AWSCredentials sessionCredentials = + new BasicSessionCredentials( + "account_b_access_key_id", "account_b_secret_access_key", "account_b_token"); + AWSCredentialsProvider sessionCredentialsProvider = + new AWSStaticCredentialsProvider(sessionCredentials); + + // Create S3 client with temporary credentials + var crossAccountS3Client = + AmazonS3Client.builder() + .withCredentials(sessionCredentialsProvider) + .withEndpointConfiguration( + new EndpointConfiguration(s3Endpoint, Regions.EU_CENTRAL_1.getName())) + .build(); + + get( + "/crossaccount/createbucket/accountb", + (req, res) -> { + CreateBucketRequest createBucketRequest = + new CreateBucketRequest("cross-account-bucket", Region.EU_Frankfurt); + crossAccountS3Client.createBucket(createBucketRequest); + return ""; + }); + } } diff --git a/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java b/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java index c96b762dfd..dc4233b107 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java @@ -35,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -68,6 +69,7 @@ import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest; import software.amazon.awssdk.services.kinesis.model.PutRecordRequest; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketConfiguration; import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @@ -148,6 +150,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupSfn(); setupBedrock(); setupSns(); + setupCrossAccount(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); logger.info("All routes initialized"); @@ -328,6 +331,36 @@ private static void setupDynamoDb() { dynamoDbClient.putItem(putItemRequest); return ""; }); + + get( + "/ddb/describetable/:tablename", + (req, res) -> { + var tableName = req.params(":tablename"); + + var createTableRequest = + CreateTableRequest.builder() + .tableName(tableName) + .attributeDefinitions( + AttributeDefinition.builder() + .attributeName("partitionKey") + .attributeType("S") + .build()) + .keySchema( + KeySchemaElement.builder() + .attributeName("partitionKey") + .keyType(KeyType.HASH) + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build()) + .build(); + dynamoDbClient.createTable(createTableRequest); + dynamoDbClient.describeTable(r -> r.tableName(tableName)); + return ""; + }); + get( "/ddb/error", (req, res) -> { @@ -413,6 +446,26 @@ private static void setupKinesis() { return ""; }); + get( + "/kinesis/describestream/:streamname", + (req, res) -> { + var streamName = req.params(":streamname"); + + var kinesisClient = + KinesisClient.builder() + .endpointOverride(endpoint) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + kinesisClient.createStream(CreateStreamRequest.builder().streamName(streamName).build()); + + // Describe stream using ARN + var streamArn = "arn:aws:kinesis:us-west-2:000000000000:stream/" + streamName; + var describeStreamRequest = DescribeStreamRequest.builder().streamARN(streamArn).build(); + kinesisClient.describeStream(describeStreamRequest); + return ""; + }); + get( "/kinesis/error", (req, res) -> { @@ -1209,4 +1262,36 @@ private static void setupBedrock() { return ""; }); } + + private static void setupCrossAccount() { + // Create credentials provider with temporary credentials + AwsSessionCredentials sessionCredentials = + AwsSessionCredentials.create( + "account_b_access_key_id", "account_b_secret_access_key", "account_b_token"); + StaticCredentialsProvider sessionCredentialsProvider = + StaticCredentialsProvider.create(sessionCredentials); + + // Create S3 client with temporary credentials + var crossAccountS3Client = + S3Client.builder() + .credentialsProvider(sessionCredentialsProvider) + .endpointOverride(s3Endpoint) + .region(Region.EU_CENTRAL_1) + .build(); + + get( + "/crossaccount/createbucket/accountb", + (req, res) -> { + CreateBucketRequest createBucketRequest = + CreateBucketRequest.builder() + .bucket("cross-account-bucket") + .createBucketConfiguration( + CreateBucketConfiguration.builder() + .locationConstraint(Region.EU_CENTRAL_1.id()) + .build()) + .build(); + crossAccountS3Client.createBucket(createBucketRequest); + return ""; + }); + } } diff --git a/awsagentprovider/build.gradle.kts b/awsagentprovider/build.gradle.kts index 37f6bf3020..1abe269cc0 100644 --- a/awsagentprovider/build.gradle.kts +++ b/awsagentprovider/build.gradle.kts @@ -45,7 +45,8 @@ dependencies { // For Udp emitter compileOnly("io.opentelemetry:opentelemetry-exporter-otlp-common") - // For OtlpAwsSpanExporter SigV4 Authentication + // For OtlpAwsExporter SigV4 Authentication + runtimeOnly("software.amazon.awssdk:sts") implementation("software.amazon.awssdk:auth") implementation("software.amazon.awssdk:http-auth-aws") diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java index 8cdd55a881..13cb4ddd81 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java @@ -135,6 +135,12 @@ public final class AwsApplicationSignalsCustomizerProvider private static final String OTEL_TRACES_SAMPLER = "otel.traces.sampler"; private static final String OTEL_TRACES_SAMPLER_ARG = "otel.traces.sampler.arg"; static final String OTEL_EXPORTER_OTLP_LOGS_HEADERS = "otel.exporter.otlp.logs.headers"; + private static final String OTEL_EXPORTER_OTLP_COMPRESSION_CONFIG = + "otel.exporter.otlp.compression"; + private static final String OTEL_EXPORTER_OTLP_TRACES_COMPRESSION_CONFIG = + "otel.exporter.otlp.traces.compression"; + private static final String OTEL_EXPORTER_OTLP_LOGS_COMPRESSION_CONFIG = + "otel.exporter.otlp.logs.compression"; // UDP packet can be upto 64KB. To limit the packet size, we limit the exported batch size. // This is a bit of a magic number, as there is no simple way to tell how many spans can make a @@ -394,11 +400,18 @@ SpanExporter customizeSpanExporter(SpanExporter spanExporter, ConfigProperties c // and OTEL_EXPORTER_OTLP_TRACES_PROTOCOL is http/protobuf // so the given spanExporter will be an instance of OtlpHttpSpanExporter + // get compression method from environment + String compression = + configProps.getString( + OTEL_EXPORTER_OTLP_TRACES_COMPRESSION_CONFIG, + configProps.getString(OTEL_EXPORTER_OTLP_COMPRESSION_CONFIG, "none")); + try { spanExporter = OtlpAwsSpanExporterBuilder.create( (OtlpHttpSpanExporter) spanExporter, configProps.getString(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)) + .setCompression(compression) .build(); } catch (Exception e) { // This technically should never happen as the validator checks for the correct env @@ -430,10 +443,17 @@ LogRecordExporter customizeLogsExporter( // OTEL_EXPORTER_OTLP_LOGS_PROTOCOL is http/protobuf // so the given logsExporter will be an instance of OtlpHttpLogRecorderExporter + // get compression method from environment + String compression = + configProps.getString( + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION_CONFIG, + configProps.getString(OTEL_EXPORTER_OTLP_COMPRESSION_CONFIG, "none")); + try { return OtlpAwsLogsExporterBuilder.create( (OtlpHttpLogRecordExporter) logsExporter, configProps.getString(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)) + .setCompression(compression) .build(); } catch (Exception e) { // This technically should never happen as the validator checks for the correct env diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeKeys.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeKeys.java index 726979dbf2..65b36ef765 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeKeys.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeKeys.java @@ -22,6 +22,11 @@ final class AwsAttributeKeys { private AwsAttributeKeys() {} + static final AttributeKey AWS_AUTH_ACCESS_KEY = + AttributeKey.stringKey("aws.auth.account.access_key"); + + static final AttributeKey AWS_AUTH_REGION = AttributeKey.stringKey("aws.auth.region"); + static final AttributeKey AWS_SPAN_KIND = AttributeKey.stringKey("aws.span.kind"); static final AttributeKey AWS_LOCAL_SERVICE = AttributeKey.stringKey("aws.local.service"); @@ -29,6 +34,19 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_LOCAL_OPERATION = AttributeKey.stringKey("aws.local.operation"); + /* + * By default the local operation of a Lambda span is hard-coded to "/FunctionHandler". + * To dynamically override this at runtime—such as when running a custom server inside your Lambda— + * you can set the span attribute "aws.lambda.local.operation.override" before ending the span. For example: + * + * // Obtain the current Span and override its operation name + * Span.current().setAttribute( + * "aws.lambda.local.operation.override", + * "MyService/handleRequest"); + */ + static final AttributeKey AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE = + AttributeKey.stringKey("aws.lambda.local.operation.override"); + static final AttributeKey AWS_REMOTE_SERVICE = AttributeKey.stringKey("aws.remote.service"); @@ -38,6 +56,15 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_REMOTE_OPERATION = AttributeKey.stringKey("aws.remote.operation"); + static final AttributeKey AWS_REMOTE_RESOURCE_ACCESS_KEY = + AttributeKey.stringKey("aws.remote.resource.account.access_key"); + + static final AttributeKey AWS_REMOTE_RESOURCE_ACCOUNT_ID = + AttributeKey.stringKey("aws.remote.resource.account.id"); + + static final AttributeKey AWS_REMOTE_RESOURCE_REGION = + AttributeKey.stringKey("aws.remote.resource.region"); + static final AttributeKey AWS_REMOTE_RESOURCE_IDENTIFIER = AttributeKey.stringKey("aws.remote.resource.identifier"); @@ -70,7 +97,7 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_LAMBDA_NAME = AttributeKey.stringKey("aws.lambda.function.name"); - static final AttributeKey AWS_LAMBDA_ARN = + static final AttributeKey AWS_LAMBDA_FUNCTION_ARN = AttributeKey.stringKey("aws.lambda.function.arn"); static final AttributeKey AWS_LAMBDA_RESOURCE_ID = @@ -87,7 +114,9 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_QUEUE_URL = AttributeKey.stringKey("aws.queue.url"); static final AttributeKey AWS_QUEUE_NAME = AttributeKey.stringKey("aws.queue.name"); static final AttributeKey AWS_STREAM_NAME = AttributeKey.stringKey("aws.stream.name"); + static final AttributeKey AWS_STREAM_ARN = AttributeKey.stringKey("aws.stream.arn"); static final AttributeKey AWS_TABLE_NAME = AttributeKey.stringKey("aws.table.name"); + static final AttributeKey AWS_TABLE_ARN = AttributeKey.stringKey("aws.table.arn"); static final AttributeKey AWS_AGENT_ID = AttributeKey.stringKey("aws.bedrock.agent.id"); static final AttributeKey AWS_KNOWLEDGE_BASE_ID = AttributeKey.stringKey("aws.bedrock.knowledge_base.id"); diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java index 2e138f6e35..a1349f06b5 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java @@ -47,13 +47,15 @@ import static io.opentelemetry.semconv.SemanticAttributes.URL_FULL; import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AUTH_ACCESS_KEY; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AUTH_REGION; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID; -import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_ARN; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_FUNCTION_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION; @@ -63,7 +65,10 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_DB_USER; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_ENVIRONMENT; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_ACCESS_KEY; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_ACCOUNT_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_REGION; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SECRET_ARN; @@ -71,7 +76,9 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STATE_MACHINE_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STEP_FUNCTIONS_ACTIVITY_ARN; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH; @@ -99,7 +106,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Level; @@ -175,7 +184,13 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re setEgressOperation(span, builder); setRemoteServiceAndOperation(span, builder); setRemoteEnvironment(span, builder); - setRemoteResourceTypeAndIdentifier(span, builder); + boolean isRemoteResourceIdentifierPresent = setRemoteResourceTypeAndIdentifier(span, builder); + if (isRemoteResourceIdentifierPresent) { + boolean isAccountIdAndRegionPresent = setRemoteResourceAccountIdAndRegion(span, builder); + if (!isAccountIdAndRegionPresent) { + setRemoteResourceAccessKeyAndRegion(span, builder); + } + } setSpanKindForDependency(span, builder); setHttpStatus(span, builder); setRemoteDbUser(span, builder); @@ -489,7 +504,8 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa * href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS * Cloud Control resource format. */ - private static void setRemoteResourceTypeAndIdentifier(SpanData span, AttributesBuilder builder) { + private static boolean setRemoteResourceTypeAndIdentifier( + SpanData span, AttributesBuilder builder) { Optional remoteResourceType = Optional.empty(); Optional remoteResourceIdentifier = Optional.empty(); Optional cloudformationPrimaryIdentifier = Optional.empty(); @@ -499,10 +515,20 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes remoteResourceType = Optional.of(NORMALIZED_DYNAMO_DB_SERVICE_NAME + "::Table"); remoteResourceIdentifier = Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_TABLE_NAME))); + } else if (isKeyPresent(span, AWS_TABLE_ARN)) { + remoteResourceType = Optional.of(NORMALIZED_DYNAMO_DB_SERVICE_NAME + "::Table"); + remoteResourceIdentifier = + getDynamodbTableNameFromArn( + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_TABLE_ARN)))); } else if (isKeyPresent(span, AWS_STREAM_NAME)) { remoteResourceType = Optional.of(NORMALIZED_KINESIS_SERVICE_NAME + "::Stream"); remoteResourceIdentifier = Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_STREAM_NAME))); + } else if (isKeyPresent(span, AWS_STREAM_ARN)) { + remoteResourceType = Optional.of(NORMALIZED_KINESIS_SERVICE_NAME + "::Stream"); + remoteResourceIdentifier = + getKinesisStreamNameFromArn( + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_STREAM_ARN)))); } else if (isKeyPresent(span, AWS_BUCKET_NAME)) { remoteResourceType = Optional.of(NORMALIZED_S3_SERVICE_NAME + "::Bucket"); remoteResourceIdentifier = @@ -579,7 +605,8 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes getLambdaFunctionNameFromArn( Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME)))); cloudformationPrimaryIdentifier = - Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_ARN))); + Optional.ofNullable( + escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_FUNCTION_ARN))); } } else if (isKeyPresent(span, AWS_LAMBDA_RESOURCE_ID)) { remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::EventSourceMapping"); @@ -599,30 +626,137 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes builder.put(AWS_REMOTE_RESOURCE_TYPE, remoteResourceType.get()); builder.put(AWS_REMOTE_RESOURCE_IDENTIFIER, remoteResourceIdentifier.get()); builder.put(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformationPrimaryIdentifier.get()); + return true; } + return false; } - private static Optional getLambdaFunctionNameFromArn(Optional stringArn) { - if (stringArn.isPresent() && stringArn.get().startsWith("arn:aws:lambda:")) { + private static void setRemoteResourceAccessKeyAndRegion( + SpanData span, AttributesBuilder builder) { + if (isKeyPresent(span, AWS_AUTH_ACCESS_KEY)) { + String remoteResourceAccessKey = span.getAttributes().get(AWS_AUTH_ACCESS_KEY); + builder.put(AWS_REMOTE_RESOURCE_ACCESS_KEY, remoteResourceAccessKey); + } + + if (isKeyPresent(span, AWS_AUTH_REGION)) { + String remoteResourceRegion = span.getAttributes().get(AWS_AUTH_REGION); + builder.put(AWS_REMOTE_RESOURCE_REGION, remoteResourceRegion); + } + } + + private static boolean setRemoteResourceAccountIdAndRegion( + SpanData span, AttributesBuilder builder) { + Optional remoteResourceAccountId = Optional.empty(); + Optional remoteResourceRegion = Optional.empty(); + List> ARN_ATTRIBUTES = + Arrays.asList( + AWS_TABLE_ARN, + AWS_STREAM_ARN, + AWS_SNS_TOPIC_ARN, + AWS_SECRET_ARN, + AWS_STEP_FUNCTIONS_ACTIVITY_ARN, + AWS_STATE_MACHINE_ARN, + AWS_GUARDRAIL_ARN, + AWS_LAMBDA_FUNCTION_ARN); + + if (isKeyPresent(span, AWS_QUEUE_URL)) { + String url = escapeDelimiters(span.getAttributes().get(AWS_QUEUE_URL)); + remoteResourceAccountId = SqsUrlParser.getAccountId(url); + remoteResourceRegion = SqsUrlParser.getRegion(url); + } else { + for (AttributeKey attributeKey : ARN_ATTRIBUTES) { + if (isKeyPresent(span, attributeKey)) { + String stringArn = escapeDelimiters(span.getAttributes().get(attributeKey)); + try { + Arn resourceArn = Arn.fromString(stringArn); + remoteResourceAccountId = Optional.of(resourceArn.getAccountId()); + remoteResourceRegion = Optional.of(resourceArn.getRegion()); + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, + String.format( + "Could not parse ARN to extract cross-account information: %s", stringArn)); + } + } + } + } + + if (remoteResourceAccountId.isPresent() && remoteResourceRegion.isPresent()) { + builder.put(AWS_REMOTE_RESOURCE_ACCOUNT_ID, remoteResourceAccountId.get()); + builder.put(AWS_REMOTE_RESOURCE_REGION, remoteResourceRegion.get()); + return true; + } + return false; + } + + private static Optional getKinesisStreamNameFromArn(Optional stringArn) { + try { Arn resourceArn = Arn.fromString(stringArn.get()); return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, String.format("Could not parse Kinesis stream name from ARN: %s", stringArn)); + } + return Optional.empty(); + } + + private static Optional getDynamodbTableNameFromArn(Optional stringArn) { + try { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, String.format("Could not parse DynamoDB table name from ARN: %s", stringArn)); + } + return Optional.empty(); + } + + private static Optional getLambdaFunctionNameFromArn(Optional stringArn) { + try { + if (stringArn.isPresent() && stringArn.get().startsWith("arn:aws:lambda:")) { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, + String.format("Could not parse Lambda resource name from ARN: %s", stringArn)); } return stringArn; } private static Optional getSecretsManagerResourceNameFromArn(Optional stringArn) { - Arn resourceArn = Arn.fromString(stringArn.get()); - return Optional.of(resourceArn.getResource().toString().split(":")[1]); + try { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, + String.format("Could not parse Secrets Manager resource name from ARN: %s", stringArn)); + } + return Optional.empty(); } private static Optional getSfnResourceNameFromArn(Optional stringArn) { - Arn resourceArn = Arn.fromString(stringArn.get()); - return Optional.of(resourceArn.getResource().toString().split(":")[1]); + try { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, String.format("Could not parse Sfn resource name from ARN: %s", stringArn)); + } + return Optional.empty(); } private static Optional getSnsResourceNameFromArn(Optional stringArn) { - Arn resourceArn = Arn.fromString(stringArn.get()); - return Optional.of(resourceArn.getResource().toString()); + try { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString()); + } catch (IllegalArgumentException e) { + logger.log( + Level.FINE, String.format("Could not parse Sfn resource name from ARN: %s", stringArn)); + } + return Optional.empty(); } /** diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java index b1f0e01b1b..539c863e23 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java @@ -27,6 +27,7 @@ import static io.opentelemetry.semconv.SemanticAttributes.URL_PATH; import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.AWS_LAMBDA_FUNCTION_NAME_CONFIG; import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.isLambdaEnvironment; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION; import com.fasterxml.jackson.core.type.TypeReference; @@ -96,11 +97,23 @@ static List getDialectKeywords() { */ static String getIngressOperation(SpanData span) { if (isLambdaEnvironment()) { - String op = generateIngressOperation(span); - if (!op.equals(UNKNOWN_OPERATION)) { - return op; + /* + * By default the local operation of a Lambda span is hard-coded to "/FunctionHandler". + * To dynamically override this at runtime—such as when running a custom server inside your Lambda— + * you can set the span attribute "aws.lambda.local.operation.override" before ending the span. For example: + * + * // Obtain the current Span and override its operation name + * Span.current().setAttribute( + * "aws.lambda.local.operation.override", + * "MyServiceOperation"); + * + * The code below will detect that override and use it instead of the default. + */ + String operationOverride = span.getAttributes().get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE); + if (operationOverride != null) { + return operationOverride; } - return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) + "/FunctionHandler"; + return getFunctionNameFromEnv() + "/FunctionHandler"; } String operation = span.getName(); if (shouldUseInternalOperation(span)) { @@ -111,6 +124,11 @@ static String getIngressOperation(SpanData span) { return operation; } + // define a function so that we can mock it in unit test + static String getFunctionNameFromEnv() { + return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG); + } + static String getEgressOperation(SpanData span) { if (shouldUseInternalOperation(span)) { return INTERNAL_OPERATION; diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParser.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParser.java index e69ccb091a..0517f9f516 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParser.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParser.java @@ -32,16 +32,54 @@ public static Optional getQueueName(String url) { if (url == null) { return Optional.empty(); } - url = url.replace(HTTP_SCHEMA, "").replace(HTTPS_SCHEMA, ""); - String[] splitUrl = url.split("/"); + String urlWithoutProtocol = url.replace(HTTP_SCHEMA, "").replace(HTTPS_SCHEMA, ""); + String[] splitUrl = urlWithoutProtocol.split("/"); if (splitUrl.length == 3 && isAccountId(splitUrl[1]) && isValidQueueName(splitUrl[2])) { return Optional.of(splitUrl[2]); } return Optional.empty(); } + /** Extracts the account ID from an SQS URL. */ + public static Optional getAccountId(String url) { + ParsedUrl parsed = parseUrl(url); + return Optional.ofNullable(parsed.accountId); + } + + /** Extracts the region from an SQS URL. */ + public static Optional getRegion(String url) { + ParsedUrl parsed = parseUrl(url); + return Optional.ofNullable(parsed.region); + } + + /** + * Parses an SQS URL and extracts its components. URL Format: + * https://sqs..amazonaws.com// + */ + private static ParsedUrl parseUrl(String url) { + if (url == null) { + return new ParsedUrl(null, null, null); + } + + String urlWithoutProtocol = url.replace(HTTP_SCHEMA, "").replace(HTTPS_SCHEMA, ""); + String[] splitUrl = urlWithoutProtocol.split("/"); + + if (splitUrl.length != 3 + || !isAccountId(splitUrl[1]) + || !isValidQueueName(splitUrl[2]) + || !splitUrl[0].toLowerCase().startsWith("sqs")) { + return new ParsedUrl(null, null, null); + } + + String domain = splitUrl[0]; + String[] domainParts = domain.split("\\."); + + String region = domainParts.length == 4 ? domainParts[1] : null; + return new ParsedUrl(splitUrl[2], splitUrl[1], region); + } + private static boolean isAccountId(String input) { - if (input == null || input.length() != 12) { + if (input == null) { return false; } @@ -67,4 +105,16 @@ private static boolean isValidQueueName(String input) { return true; } + + private static class ParsedUrl { + private final String queueName; + private final String accountId; + private final String region; + + private ParsedUrl(String queueName, String accountId, String region) { + this.queueName = queueName; + this.accountId = accountId; + this.region = region; + } + } } diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/SigV4AuthHeaderSupplier.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/AwsAuthHeaderSupplier.java similarity index 71% rename from awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/SigV4AuthHeaderSupplier.java rename to awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/AwsAuthHeaderSupplier.java index ca34d829ec..b47e0916ae 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/SigV4AuthHeaderSupplier.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/AwsAuthHeaderSupplier.java @@ -16,11 +16,14 @@ package software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.URI; import java.util.*; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.zip.GZIPOutputStream; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.http.SdkHttpFullRequest; @@ -29,11 +32,11 @@ import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; -final class SigV4AuthHeaderSupplier implements Supplier> { +final class AwsAuthHeaderSupplier implements Supplier> { BaseOtlpAwsExporter exporter; Logger logger; - public SigV4AuthHeaderSupplier(BaseOtlpAwsExporter exporter) { + public AwsAuthHeaderSupplier(BaseOtlpAwsExporter exporter) { this.exporter = exporter; this.logger = Logger.getLogger(exporter.getClass().getName()); } @@ -41,7 +44,7 @@ public SigV4AuthHeaderSupplier(BaseOtlpAwsExporter exporter) { @Override public Map get() { try { - byte[] data = exporter.data.get(); + ByteArrayOutputStream data = exporter.data.get(); SdkHttpRequest httpRequest = SdkHttpFullRequest.builder() @@ -50,6 +53,14 @@ public Map get() { .putHeader("Content-Type", "application/x-protobuf") .build(); + // Compress the data before signing with gzip + ByteArrayOutputStream compressedData; + if (exporter.getCompression().equals(CompressionMethod.GZIP)) { + compressedData = compressWithGzip(data); + } else { + compressedData = data; + } + AwsCredentials credentials = DefaultCredentialsProvider.create().resolveCredentials(); SignedRequest signedRequest = @@ -60,7 +71,7 @@ public Map get() { .request(httpRequest) .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, exporter.serviceName()) .putProperty(AwsV4HttpSigner.REGION_NAME, exporter.awsRegion) - .payload(() -> new ByteArrayInputStream(data))); + .payload(() -> new ByteArrayInputStream(compressedData.toByteArray()))); Map result = new HashMap<>(); @@ -84,4 +95,21 @@ public Map get() { return Collections.emptyMap(); } } + + /** + * Compresses the given byte array using GZIP compression. + * + * @param data the byte array stream to compress + * @return the compressed byte as a ByteArrayOutputStream + * @throws IOException if compression fails + */ + private ByteArrayOutputStream compressWithGzip(ByteArrayOutputStream data) throws IOException { + ByteArrayOutputStream compressedData = new ByteArrayOutputStream(); + + try (GZIPOutputStream gzipOut = new GZIPOutputStream(compressedData)) { + data.writeTo(gzipOut); + } + + return compressedData; + } } diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/BaseOtlpAwsExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/BaseOtlpAwsExporter.java index 864f4c0a82..08ae2cc618 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/BaseOtlpAwsExporter.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/BaseOtlpAwsExporter.java @@ -15,6 +15,7 @@ package software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common; +import java.io.ByteArrayOutputStream; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -27,15 +28,21 @@ public abstract class BaseOtlpAwsExporter { protected final String awsRegion; protected final String endpoint; - protected final AtomicReference data; + protected final AtomicReference data; protected final Supplier> headerSupplier; + protected final CompressionMethod compression; - protected BaseOtlpAwsExporter(String endpoint) { + protected BaseOtlpAwsExporter(String endpoint, CompressionMethod compression) { this.endpoint = endpoint.toLowerCase(); + this.compression = compression; this.awsRegion = endpoint.split("\\.")[1]; this.data = new AtomicReference<>(); - this.headerSupplier = new SigV4AuthHeaderSupplier(this); + this.headerSupplier = new AwsAuthHeaderSupplier(this); } public abstract String serviceName(); + + public CompressionMethod getCompression() { + return this.compression; + } } diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/CompressionMethod.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/CompressionMethod.java new file mode 100644 index 0000000000..ae63dd12ff --- /dev/null +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/common/CompressionMethod.java @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common; + +public enum CompressionMethod { + NONE, + GZIP +} diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java index 1f1bd2a006..f93b1f1c9a 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporter.java @@ -28,6 +28,7 @@ import java.util.StringJoiner; import javax.annotation.Nonnull; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.BaseOtlpAwsExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; /** * This exporter extends the functionality of the OtlpHttpLogsRecordExporter to allow logs to be @@ -42,19 +43,18 @@ public final class OtlpAwsLogsExporter extends BaseOtlpAwsExporter implements Lo private final OtlpHttpLogRecordExporter parentExporter; static OtlpAwsLogsExporter getDefault(String endpoint) { - return new OtlpAwsLogsExporter(endpoint); + return new OtlpAwsLogsExporter( + OtlpHttpLogRecordExporter.getDefault(), endpoint, CompressionMethod.NONE); } - static OtlpAwsLogsExporter create(OtlpHttpLogRecordExporter parent, String endpoint) { - return new OtlpAwsLogsExporter(parent, endpoint); + static OtlpAwsLogsExporter create( + OtlpHttpLogRecordExporter parent, String endpoint, CompressionMethod compression) { + return new OtlpAwsLogsExporter(parent, endpoint, compression); } - private OtlpAwsLogsExporter(String endpoint) { - this(OtlpHttpLogRecordExporter.getDefault(), endpoint); - } - - private OtlpAwsLogsExporter(OtlpHttpLogRecordExporter parentExporter, String endpoint) { - super(endpoint); + private OtlpAwsLogsExporter( + OtlpHttpLogRecordExporter parentExporter, String endpoint, CompressionMethod compression) { + super(endpoint, compression); this.parentExporterBuilder = parentExporter.toBuilder() @@ -75,7 +75,7 @@ public CompletableResultCode export(@Nonnull Collection logs) { try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); LogsRequestMarshaler.create(logs).writeBinaryTo(buffer); - this.data.set(buffer.toByteArray()); + this.data.set(buffer); return this.parentExporter.export(logs); } catch (IOException e) { return CompletableResultCode.ofFailure(); diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java index 440dce6d79..bf91bd6d4e 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/logs/OtlpAwsLogsExporterBuilder.java @@ -18,10 +18,12 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; public class OtlpAwsLogsExporterBuilder { private final OtlpHttpLogRecordExporter parentExporter; private final String endpoint; + private String compression; public static OtlpAwsLogsExporterBuilder create( OtlpHttpLogRecordExporter parentExporter, String endpoint) { @@ -32,8 +34,18 @@ public static OtlpAwsLogsExporter getDefault(String endpoint) { return OtlpAwsLogsExporter.getDefault(endpoint); } + public OtlpAwsLogsExporterBuilder setCompression(String compression) { + this.compression = compression; + return this; + } + public OtlpAwsLogsExporter build() { - return OtlpAwsLogsExporter.create(this.parentExporter, this.endpoint); + CompressionMethod compression = CompressionMethod.NONE; + if (this.compression != null && "gzip".equalsIgnoreCase(this.compression)) { + compression = CompressionMethod.GZIP; + } + + return OtlpAwsLogsExporter.create(this.parentExporter, this.endpoint, compression); } private OtlpAwsLogsExporterBuilder(OtlpHttpLogRecordExporter parentExporter, String endpoint) { diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporter.java index d2feba84a1..ff0dcf4cb3 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporter.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporter.java @@ -28,6 +28,7 @@ import java.util.StringJoiner; import javax.annotation.Nonnull; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.BaseOtlpAwsExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; /** * This exporter extends the functionality of the OtlpHttpSpanExporter to allow spans to be exported @@ -40,19 +41,18 @@ public final class OtlpAwsSpanExporter extends BaseOtlpAwsExporter implements Sp private final OtlpHttpSpanExporter parentExporter; static OtlpAwsSpanExporter getDefault(String endpoint) { - return new OtlpAwsSpanExporter(endpoint); + return new OtlpAwsSpanExporter( + OtlpHttpSpanExporter.getDefault(), endpoint, CompressionMethod.NONE); } - static OtlpAwsSpanExporter create(OtlpHttpSpanExporter parent, String endpoint) { - return new OtlpAwsSpanExporter(parent, endpoint); + static OtlpAwsSpanExporter create( + OtlpHttpSpanExporter parent, String endpoint, CompressionMethod compression) { + return new OtlpAwsSpanExporter(parent, endpoint, compression); } - private OtlpAwsSpanExporter(String endpoint) { - this(OtlpHttpSpanExporter.getDefault(), endpoint); - } - - private OtlpAwsSpanExporter(OtlpHttpSpanExporter parentExporter, String endpoint) { - super(endpoint); + private OtlpAwsSpanExporter( + OtlpHttpSpanExporter parentExporter, String endpoint, CompressionMethod compression) { + super(endpoint, compression); this.parentExporterBuilder = parentExporter.toBuilder() @@ -73,7 +73,7 @@ public CompletableResultCode export(@Nonnull Collection spans) { try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); TraceRequestMarshaler.create(spans).writeBinaryTo(buffer); - this.data.set(buffer.toByteArray()); + this.data.set(buffer); return this.parentExporter.export(spans); } catch (IOException e) { return CompletableResultCode.ofFailure(); diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporterBuilder.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporterBuilder.java index 1b0c725136..bef2d5a589 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporterBuilder.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/otlp/aws/traces/OtlpAwsSpanExporterBuilder.java @@ -18,10 +18,12 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; public class OtlpAwsSpanExporterBuilder { private final OtlpHttpSpanExporter parentExporter; private final String endpoint; + private String compression; public static OtlpAwsSpanExporterBuilder create( OtlpHttpSpanExporter parentExporter, String endpoint) { @@ -32,12 +34,22 @@ public static OtlpAwsSpanExporter getDefault(String endpoint) { return OtlpAwsSpanExporter.getDefault(endpoint); } + public OtlpAwsSpanExporterBuilder setCompression(String compression) { + this.compression = compression; + return this; + } + private OtlpAwsSpanExporterBuilder(OtlpHttpSpanExporter parentExporter, String endpoint) { this.parentExporter = requireNonNull(parentExporter, "Must set a parentExporter"); this.endpoint = requireNonNull(endpoint, "Must set an endpoint"); } public OtlpAwsSpanExporter build() { - return OtlpAwsSpanExporter.create(this.parentExporter, this.endpoint); + CompressionMethod compression = CompressionMethod.NONE; + if (this.compression != null && "gzip".equalsIgnoreCase(this.compression)) { + compression = CompressionMethod.GZIP; + } + + return OtlpAwsSpanExporter.create(this.parentExporter, this.endpoint, compression); } } diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java index 2811bec965..8b362193df 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java @@ -22,13 +22,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AUTH_ACCESS_KEY; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AUTH_REGION; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID; -import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_ARN; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_FUNCTION_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION; @@ -38,7 +40,10 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_DB_USER; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_ENVIRONMENT; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_ACCESS_KEY; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_ACCOUNT_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_REGION; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SECRET_ARN; @@ -46,7 +51,9 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STATE_MACHINE_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STEP_FUNCTIONS_ACTIVITY_ARN; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_ARN; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL; import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.DEPENDENCY_METRIC; @@ -65,6 +72,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -85,6 +93,8 @@ class AwsMetricAttributeGeneratorTest { private static final String UNKNOWN_REMOTE_OPERATION = "UnknownRemoteOperation"; private static final String INTERNAL_OPERATION = "InternalOperation"; private static final String LOCAL_ROOT = "LOCAL_ROOT"; + private static final String MOCK_ACCESS_KEY = "MockAccessKey"; + private static final String MOCK_REGION = "us-east-1"; private Attributes attributesMock; private SpanData spanDataMock; @@ -758,14 +768,20 @@ public void testPeerServiceDoesNotOverrideAwsRemoteService() { @Test public void testSdkClientSpanWithRemoteResourceAttributes() { mockAttribute(RPC_SYSTEM, "aws-api"); + mockAttribute(AWS_AUTH_ACCESS_KEY, MOCK_ACCESS_KEY); + mockAttribute(AWS_AUTH_REGION, MOCK_REGION); // Validate behaviour of aws bucket name attribute, then remove it. mockAttribute(AWS_BUCKET_NAME, "aws_s3_bucket_name"); validateRemoteResourceAttributes("AWS::S3::Bucket", "aws_s3_bucket_name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_BUCKET_NAME, null); // Validate behaviour of AWS_QUEUE_NAME attribute, then remove it. mockAttribute(AWS_QUEUE_NAME, "aws_queue_name"); validateRemoteResourceAttributes("AWS::SQS::Queue", "aws_queue_name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_QUEUE_NAME, null); // Validate behaviour of having both AWS_QUEUE_NAME and AWS_QUEUE_URL attribute, then remove @@ -773,6 +789,8 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { mockAttribute(AWS_QUEUE_URL, "https://sqs.us-east-2.amazonaws.com/123456789012/Queue"); mockAttribute(AWS_QUEUE_NAME, "aws_queue_name"); validateRemoteResourceAttributes("AWS::SQS::Queue", "aws_queue_name"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-2")); mockAttribute(AWS_QUEUE_URL, null); mockAttribute(AWS_QUEUE_NAME, null); @@ -780,63 +798,98 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { mockAttribute(AWS_QUEUE_URL, "invalidUrl"); mockAttribute(AWS_QUEUE_NAME, "aws_queue_name"); validateRemoteResourceAttributes("AWS::SQS::Queue", "aws_queue_name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_QUEUE_URL, null); mockAttribute(AWS_QUEUE_NAME, null); // Validate behaviour of AWS_STREAM_NAME attribute, then remove it. mockAttribute(AWS_STREAM_NAME, "aws_stream_name"); validateRemoteResourceAttributes("AWS::Kinesis::Stream", "aws_stream_name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_STREAM_NAME, null); + // Validate behaviour of AWS_STREAM_ARN attribute, then remove it. + mockAttribute(AWS_STREAM_ARN, "arn:aws:kinesis:us-east-1:123456789012:stream/test_stream"); + validateRemoteResourceAttributes("AWS::Kinesis::Stream", "test_stream"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); + mockAttribute(AWS_STREAM_ARN, null); + // Validate behaviour of AWS_TABLE_NAME attribute, then remove it. mockAttribute(AWS_TABLE_NAME, "aws_table_name"); validateRemoteResourceAttributes("AWS::DynamoDB::Table", "aws_table_name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_TABLE_NAME, null); // Validate behaviour of AWS_TABLE_NAME attribute with special chars(|), then remove it. mockAttribute(AWS_TABLE_NAME, "aws_table|name"); validateRemoteResourceAttributes("AWS::DynamoDB::Table", "aws_table^|name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_TABLE_NAME, null); // Validate behaviour of AWS_TABLE_NAME attribute with special chars(^), then remove it. mockAttribute(AWS_TABLE_NAME, "aws_table^name"); validateRemoteResourceAttributes("AWS::DynamoDB::Table", "aws_table^^name"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_TABLE_NAME, null); + // Validate behaviour of AWS_TABLE_ARN attribute, then remove it. + mockAttribute(AWS_TABLE_ARN, "arn:aws:dynamodb:us-east-1:123456789012:table/test_table"); + validateRemoteResourceAttributes("AWS::DynamoDB::Table", "test_table"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); + mockAttribute(AWS_TABLE_ARN, null); + // Validate behaviour of AWS_BEDROCK_AGENT_ID attribute, then remove it. mockAttribute(AWS_AGENT_ID, "test_agent_id"); validateRemoteResourceAttributes("AWS::Bedrock::Agent", "test_agent_id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_AGENT_ID, null); // Validate behaviour of AWS_BEDROCK_AGENT_ID attribute with special chars(^), then remove it. mockAttribute(AWS_AGENT_ID, "test_agent_^id"); validateRemoteResourceAttributes("AWS::Bedrock::Agent", "test_agent_^^id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_AGENT_ID, null); // Validate behaviour of AWS_KNOWLEDGE_BASE_ID attribute, then remove it. mockAttribute(AWS_KNOWLEDGE_BASE_ID, "test_knowledgeBase_id"); validateRemoteResourceAttributes("AWS::Bedrock::KnowledgeBase", "test_knowledgeBase_id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_KNOWLEDGE_BASE_ID, null); // Validate behaviour of AWS_KNOWLEDGE_BASE_ID attribute with special chars(^), then remove it. mockAttribute(AWS_KNOWLEDGE_BASE_ID, "test_knowledgeBase_^id"); validateRemoteResourceAttributes("AWS::Bedrock::KnowledgeBase", "test_knowledgeBase_^^id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_KNOWLEDGE_BASE_ID, null); // Validate behaviour of AWS_DATA_SOURCE_ID attribute, then remove it. mockAttribute(AWS_DATA_SOURCE_ID, "test_datasource_id"); validateRemoteResourceAttributes("AWS::Bedrock::DataSource", "test_datasource_id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_DATA_SOURCE_ID, null); // Validate behaviour of AWS_DATA_SOURCE_ID attribute with special chars(^), then remove // it. mockAttribute(AWS_DATA_SOURCE_ID, "test_datasource_^id"); validateRemoteResourceAttributes("AWS::Bedrock::DataSource", "test_datasource_^^id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_DATA_SOURCE_ID, null); // Validate behaviour of AWS_GUARDRAIL_ID attribute, then remove it. mockAttribute(AWS_GUARDRAIL_ID, "test_guardrail_id"); - validateRemoteResourceAttributes("AWS::Bedrock::Guardrail", "test_guardrail_id"); // Also test with ARN to verify cloudformationPrimaryIdentifier uses ARN mockAttribute( AWS_GUARDRAIL_ARN, "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_id"); @@ -844,12 +897,13 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { "AWS::Bedrock::Guardrail", "test_guardrail_id", "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_id"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); mockAttribute(AWS_GUARDRAIL_ID, null); mockAttribute(AWS_GUARDRAIL_ARN, null); // Validate behaviour of AWS_GUARDRAIL_ID attribute with special chars(^), then remove it. mockAttribute(AWS_GUARDRAIL_ID, "test_guardrail_^id"); - validateRemoteResourceAttributes("AWS::Bedrock::Guardrail", "test_guardrail_^^id"); // Also test with ARN containing special chars to verify delimiter escaping in // cloudformationPrimaryIdentifier mockAttribute( @@ -858,24 +912,32 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { "AWS::Bedrock::Guardrail", "test_guardrail_^^id", "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_^^id"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); mockAttribute(AWS_GUARDRAIL_ID, null); mockAttribute(AWS_GUARDRAIL_ARN, null); // Validate behaviour of GEN_AI_REQUEST_MODEL attribute, then remove it. mockAttribute(GEN_AI_REQUEST_MODEL, "test.service_id"); validateRemoteResourceAttributes("AWS::Bedrock::Model", "test.service_id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(GEN_AI_REQUEST_MODEL, null); // Validate behaviour of GEN_AI_REQUEST_MODEL attribute with special chars(^), then // remove it. mockAttribute(GEN_AI_REQUEST_MODEL, "test.service_^id"); validateRemoteResourceAttributes("AWS::Bedrock::Model", "test.service_^^id"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(GEN_AI_REQUEST_MODEL, null); // Validate behaviour of AWS_STATE_MACHINE_ARN attribute, then remove it. mockAttribute( AWS_STATE_MACHINE_ARN, "arn:aws:states:us-east-1:123456789012:stateMachine:test_state_machine"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); validateRemoteResourceAttributes( "AWS::StepFunctions::StateMachine", "test_state_machine", @@ -885,15 +947,21 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { // Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN, then remove it. mockAttribute( AWS_STEP_FUNCTIONS_ACTIVITY_ARN, - "arn:aws:states:us-east-1:007003123456789012:activity:testActivity"); + "arn:aws:states:us-east-1:123456789012:activity:testActivity"); + mockAttribute(AWS_AUTH_ACCESS_KEY, MOCK_ACCESS_KEY); + mockAttribute(AWS_AUTH_REGION, MOCK_REGION); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); validateRemoteResourceAttributes( "AWS::StepFunctions::Activity", "testActivity", - "arn:aws:states:us-east-1:007003123456789012:activity:testActivity"); + "arn:aws:states:us-east-1:123456789012:activity:testActivity"); mockAttribute(AWS_STEP_FUNCTIONS_ACTIVITY_ARN, null); // Validate behaviour of AWS_SNS_TOPIC_ARN, then remove it. mockAttribute(AWS_SNS_TOPIC_ARN, "arn:aws:sns:us-west-2:012345678901:testTopic"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("012345678901"), Optional.empty(), Optional.of("us-west-2")); validateRemoteResourceAttributes( "AWS::SNS::Topic", "testTopic", "arn:aws:sns:us-west-2:012345678901:testTopic"); mockAttribute(AWS_SNS_TOPIC_ARN, null); @@ -901,6 +969,8 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { // Validate behaviour of AWS_SECRET_ARN, then remove it. mockAttribute( AWS_SECRET_ARN, "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretName"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); validateRemoteResourceAttributes( "AWS::SecretsManager::Secret", "secretName", @@ -911,29 +981,35 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { mockAttribute(RPC_SERVICE, "Lambda"); mockAttribute(RPC_METHOD, "GetFunction"); mockAttribute(AWS_LAMBDA_NAME, "testLambdaName"); - mockAttribute(AWS_LAMBDA_ARN, "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); + mockAttribute( + AWS_LAMBDA_FUNCTION_ARN, "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); validateRemoteResourceAttributes( "AWS::Lambda::Function", "testLambdaName", "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); mockAttribute(RPC_SERVICE, null); mockAttribute(RPC_METHOD, null); mockAttribute(AWS_LAMBDA_NAME, null); - mockAttribute(AWS_LAMBDA_ARN, null); + mockAttribute(AWS_LAMBDA_FUNCTION_ARN, null); // Validate behaviour of AWS_LAMBDA_NAME containing ARN for non-Invoke operations mockAttribute(RPC_SERVICE, "Lambda"); mockAttribute(RPC_METHOD, "ListFunctions"); mockAttribute(AWS_LAMBDA_NAME, "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); - mockAttribute(AWS_LAMBDA_ARN, "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); + mockAttribute( + AWS_LAMBDA_FUNCTION_ARN, "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); validateRemoteResourceAttributes( "AWS::Lambda::Function", "testLambdaName", "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); mockAttribute(RPC_SERVICE, null); mockAttribute(RPC_METHOD, null); mockAttribute(AWS_LAMBDA_NAME, null); - mockAttribute(AWS_LAMBDA_ARN, null); + mockAttribute(AWS_LAMBDA_FUNCTION_ARN, null); // Validate that Lambda Invoke with function name treats Lambda as a service, not a resource mockAttribute(RPC_SERVICE, "Lambda"); @@ -956,8 +1032,64 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { // Validate behaviour of AWS_LAMBDA_RESOURCE_ID mockAttribute(AWS_LAMBDA_RESOURCE_ID, "eventSourceId"); validateRemoteResourceAttributes("AWS::Lambda::EventSourceMapping", "eventSourceId"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); mockAttribute(AWS_LAMBDA_RESOURCE_ID, null); + // Validate behaviour of AWS_LAMBDA_FUNCTION_NAME + mockAttribute(AWS_LAMBDA_RESOURCE_ID, "eventSourceId"); + validateRemoteResourceAttributes("AWS::Lambda::EventSourceMapping", "eventSourceId"); + validateRemoteResourceAccountIdAndRegion( + Optional.empty(), Optional.of(MOCK_ACCESS_KEY), Optional.of(MOCK_REGION)); + mockAttribute(AWS_LAMBDA_RESOURCE_ID, null); + + // Cross account support + // Invalid arn but account access key is available + mockAttribute(AWS_SECRET_ARN, "invalid_arn"); + validateRemoteResourceAccountIdAndRegion(Optional.empty(), Optional.empty(), Optional.empty()); + mockAttribute(AWS_SECRET_ARN, null); + + // Both account access key and account id are not available + mockAttribute(AWS_AUTH_REGION, null); + mockAttribute(AWS_AUTH_ACCESS_KEY, null); + mockAttribute(AWS_BUCKET_NAME, "aws_s3_bucket_name"); + validateRemoteResourceAttributes("AWS::S3::Bucket", "aws_s3_bucket_name"); + validateRemoteResourceAccountIdAndRegion(Optional.empty(), Optional.empty(), Optional.empty()); + mockAttribute(AWS_BUCKET_NAME, null); + + // Account access key is not available + mockAttribute( + AWS_SECRET_ARN, "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretName"); + validateRemoteResourceAttributes( + "AWS::SecretsManager::Secret", + "secretName", + "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretName"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("us-east-1")); + mockAttribute(AWS_SECRET_ARN, null); + + // Arn with invalid account id + mockAttribute( + AWS_SECRET_ARN, "arn:aws:secretsmanager:us-east-1:invalid_account_id:secret:secretName"); + validateRemoteResourceAttributes( + "AWS::SecretsManager::Secret", + "secretName", + "arn:aws:secretsmanager:us-east-1:invalid_account_id:secret:secretName"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("invalid_account_id"), Optional.empty(), Optional.of("us-east-1")); + mockAttribute(AWS_SECRET_ARN, null); + + // Arn with invalid region + mockAttribute( + AWS_SECRET_ARN, "arn:aws:secretsmanager:invalid_region:123456789012:secret:secretName"); + validateRemoteResourceAttributes( + "AWS::SecretsManager::Secret", + "secretName", + "arn:aws:secretsmanager:invalid_region:123456789012:secret:secretName"); + validateRemoteResourceAccountIdAndRegion( + Optional.of("123456789012"), Optional.empty(), Optional.of("invalid_region")); + mockAttribute(AWS_SECRET_ARN, null); + mockAttribute(RPC_SYSTEM, "null"); } @@ -1284,6 +1416,47 @@ private void validatePeerServiceDoesOverride(AttributeKey remoteServiceK mockAttribute(PEER_SERVICE, null); } + private void validateRemoteResourceAccountIdAndRegion( + Optional accountId, Optional accessKey, Optional region) { + SpanKind[] spanKinds = {SpanKind.CLIENT, SpanKind.PRODUCER, SpanKind.CONSUMER}; + + for (SpanKind spanKind : spanKinds) { + when(spanDataMock.getKind()).thenReturn(spanKind); + Attributes actualAttributes = + GENERATOR + .generateMetricAttributeMapFromSpan(spanDataMock, resource) + .get(DEPENDENCY_METRIC); + + if (region.isPresent()) { + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_REGION)).isEqualTo(region.get()); + } else { + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_REGION)).isEqualTo(null); + } + + if (accountId.isPresent()) { + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCOUNT_ID)).isEqualTo(accountId.get()); + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCESS_KEY)).isEqualTo(null); + } else { + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCOUNT_ID)).isEqualTo(null); + } + + if (accessKey.isPresent()) { + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCESS_KEY)).isEqualTo(accessKey.get()); + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCOUNT_ID)).isEqualTo(null); + } else { + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCESS_KEY)).isEqualTo(null); + } + } + + // Server span should not generate remote resource attributes + when(spanDataMock.getKind()).thenReturn(SpanKind.SERVER); + Attributes actualAttributes = + GENERATOR.generateMetricAttributeMapFromSpan(spanDataMock, resource).get(SERVICE_METRIC); + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCESS_KEY)).isNull(); + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_ACCOUNT_ID)).isNull(); + assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_REGION)).isNull(); + } + private void validateRemoteResourceAttributes(String type, String identifier) { validateRemoteResourceAttributes(type, identifier, identifier); } diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java index f3500a2aa6..ea576a7303 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java @@ -21,8 +21,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Answers.CALLS_REAL_METHODS; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.getDialectKeywords; @@ -36,6 +40,7 @@ import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; public class AwsSpanProcessingUtilTest { private static final String DEFAULT_PATH_VALUE = "/"; @@ -123,6 +128,49 @@ public void testGetIngressOperationInvalidNameAndValidTargetAndMethod() { assertThat(actualOperation).isEqualTo(validMethod + " " + validTarget); } + @Test + public void testGetIngressOperationLambdaOverride() { + try (MockedStatic providerStatic = + mockStatic( + AwsApplicationSignalsCustomizerProvider.class, + withSettings().defaultAnswer(CALLS_REAL_METHODS))) { + // Force Lambda environment branch + providerStatic + .when(AwsApplicationSignalsCustomizerProvider::isLambdaEnvironment) + .thenReturn(true); + // Simulate an override attribute on the span + when(attributesMock.get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE)).thenReturn("MyOverrideOp"); + + String actualOperation = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + assertThat(actualOperation).isEqualTo("MyOverrideOp"); + } + } + + @Test + public void testGetIngressOperationLambdaDefault() throws Exception { + try ( + // Mock the AWS environment check + MockedStatic providerStatic = + mockStatic( + AwsApplicationSignalsCustomizerProvider.class, + withSettings().defaultAnswer(CALLS_REAL_METHODS)); + // Mock only getFunctionNameFromEnv, leave all other util logic untouched + MockedStatic utilStatic = + mockStatic( + AwsSpanProcessingUtil.class, withSettings().defaultAnswer(CALLS_REAL_METHODS))) { + // force lambda branch and no override attribute + providerStatic + .when(AwsApplicationSignalsCustomizerProvider::isLambdaEnvironment) + .thenReturn(true); + when(attributesMock.get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE)).thenReturn(null); + // Provide a deterministic function name + utilStatic.when(AwsSpanProcessingUtil::getFunctionNameFromEnv).thenReturn("MockFunction"); + + String actual = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + assertThat(actual).isEqualTo("MockFunction/FunctionHandler"); + } + } + @Test public void testGetEgressOperationUseInternalOperation() { String invalidName = null; diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsExporterTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsExporterTest.java index 69f6ab029c..0af51a345a 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsExporterTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsExporterTest.java @@ -50,7 +50,10 @@ import software.amazon.awssdk.http.auth.spi.signer.SignRequest.Builder; import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.common.CompressionMethod; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.logs.OtlpAwsLogsExporterBuilder; +import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporter; import software.amazon.opentelemetry.javaagent.providers.exporter.otlp.aws.traces.OtlpAwsSpanExporterBuilder; interface OtlpAwsExporterTest { @@ -215,15 +218,51 @@ static class OtlpAwsSpanExporterTest extends AbstractOtlpAwsExporterTest { @BeforeEach @Override void setup() { - when(this.mockExporter.toBuilder()).thenReturn(mockBuilder); - when(this.mockBuilder.setEndpoint(any())).thenReturn(mockBuilder); - when(this.mockBuilder.setMemoryMode(any())).thenReturn(this.mockBuilder); - when(this.mockBuilder.setHeaders(this.headersCaptor.capture())).thenReturn(mockBuilder); - when(this.mockBuilder.build()).thenReturn(this.mockExporter); + lenient().when(this.mockExporter.toBuilder()).thenReturn(mockBuilder); + lenient().when(this.mockBuilder.setEndpoint(any())).thenReturn(mockBuilder); + lenient().when(this.mockBuilder.setMemoryMode(any())).thenReturn(this.mockBuilder); + lenient() + .when(this.mockBuilder.setHeaders(this.headersCaptor.capture())) + .thenReturn(mockBuilder); + lenient().when(this.mockBuilder.build()).thenReturn(this.mockExporter); OtlpAwsExporterTest tester = new MockOtlpAwsSpanExporterWrapper(this.mockExporter); this.init(XRAY_OTLP_ENDPOINT, tester); super.setup(); - when(this.mockExporter.export(any())).thenReturn(CompletableResultCode.ofSuccess()); + lenient().when(this.mockExporter.export(any())).thenReturn(CompletableResultCode.ofSuccess()); + } + + @Test + void testSpanExporterCompressionDefaultsToNone() { + OtlpAwsSpanExporter exporter = + OtlpAwsSpanExporterBuilder.create(this.mockExporter, XRAY_OTLP_ENDPOINT).build(); + assertEquals(CompressionMethod.NONE, exporter.getCompression()); + } + + @Test + void testSpanExporterCompressionCanBeSetToGzip() { + OtlpAwsSpanExporter exporter = + OtlpAwsSpanExporterBuilder.create(this.mockExporter, XRAY_OTLP_ENDPOINT) + .setCompression("gzip") + .build(); + assertEquals(CompressionMethod.GZIP, exporter.getCompression()); + } + + @Test + void testSpanExporterCompressionIgnoresCaseForGzip() { + OtlpAwsSpanExporter exporter = + OtlpAwsSpanExporterBuilder.create(this.mockExporter, XRAY_OTLP_ENDPOINT) + .setCompression("GZIP") + .build(); + assertEquals(CompressionMethod.GZIP, exporter.getCompression()); + } + + @Test + void testSpanExporterCompressionDefaultsToNoneForUnknownValue() { + OtlpAwsSpanExporter exporter = + OtlpAwsSpanExporterBuilder.create(this.mockExporter, XRAY_OTLP_ENDPOINT) + .setCompression("unknown") + .build(); + assertEquals(CompressionMethod.NONE, exporter.getCompression()); } private static final class MockOtlpAwsSpanExporterWrapper implements OtlpAwsExporterTest { @@ -252,15 +291,51 @@ static class OtlpAwsLogsExporterTest extends AbstractOtlpAwsExporterTest { @BeforeEach @Override void setup() { - when(this.mockExporter.toBuilder()).thenReturn(mockBuilder); - when(this.mockBuilder.setEndpoint(any())).thenReturn(mockBuilder); - when(this.mockBuilder.setMemoryMode(any())).thenReturn(this.mockBuilder); - when(this.mockBuilder.setHeaders(this.headersCaptor.capture())).thenReturn(mockBuilder); - when(this.mockBuilder.build()).thenReturn(this.mockExporter); + lenient().when(this.mockExporter.toBuilder()).thenReturn(mockBuilder); + lenient().when(this.mockBuilder.setEndpoint(any())).thenReturn(mockBuilder); + lenient().when(this.mockBuilder.setMemoryMode(any())).thenReturn(this.mockBuilder); + lenient() + .when(this.mockBuilder.setHeaders(this.headersCaptor.capture())) + .thenReturn(mockBuilder); + lenient().when(this.mockBuilder.build()).thenReturn(this.mockExporter); OtlpAwsExporterTest mocker = new MockOtlpAwsLogsExporterWrapper(this.mockExporter); this.init(LOGS_OTLP_ENDPOINT, mocker); super.setup(); - when(this.mockExporter.export(any())).thenReturn(CompletableResultCode.ofSuccess()); + lenient().when(this.mockExporter.export(any())).thenReturn(CompletableResultCode.ofSuccess()); + } + + @Test + void testLogsExporterCompressionDefaultsToNone() { + OtlpAwsLogsExporter exporter = + OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT).build(); + assertEquals(CompressionMethod.NONE, exporter.getCompression()); + } + + @Test + void testLogsExporterCompressionCanBeSetToGzip() { + OtlpAwsLogsExporter exporter = + OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) + .setCompression("gzip") + .build(); + assertEquals(CompressionMethod.GZIP, exporter.getCompression()); + } + + @Test + void testLogsExporterCompressionIgnoresCaseForGzip() { + OtlpAwsLogsExporter exporter = + OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) + .setCompression("GZIP") + .build(); + assertEquals(CompressionMethod.GZIP, exporter.getCompression()); + } + + @Test + void testLogsExporterCompressionDefaultsToNoneForUnknownValue() { + OtlpAwsLogsExporter exporter = + OtlpAwsLogsExporterBuilder.create(this.mockExporter, LOGS_OTLP_ENDPOINT) + .setCompression("unknown") + .build(); + assertEquals(CompressionMethod.NONE, exporter.getCompression()); } private static final class MockOtlpAwsLogsExporterWrapper implements OtlpAwsExporterTest { diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParserTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParserTest.java index 551d02b7b5..d5aaff3122 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParserTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/SqsUrlParserTest.java @@ -24,59 +24,105 @@ public class SqsUrlParserTest { @Test public void testSqsClientSpanBasicUrls() { - validate("https://sqs.us-east-1.amazonaws.com/123412341234/Q_Name-5", "Q_Name-5"); - validate("https://sqs.af-south-1.amazonaws.com/999999999999/-_ThisIsValid", "-_ThisIsValid"); - validate("http://sqs.eu-west-3.amazonaws.com/000000000000/FirstQueue", "FirstQueue"); - validate("sqs.sa-east-1.amazonaws.com/123456781234/SecondQueue", "SecondQueue"); + validateGetQueueName("https://sqs.us-east-1.amazonaws.com/123412341234/Q_Name-5", "Q_Name-5"); + validateGetQueueName( + "https://sqs.af-south-1.amazonaws.com/999999999999/-_ThisIsValid", "-_ThisIsValid"); + validateGetQueueName( + "http://sqs.eu-west-3.amazonaws.com/000000000000/FirstQueue", "FirstQueue"); + validateGetQueueName("sqs.sa-east-1.amazonaws.com/123456781234/SecondQueue", "SecondQueue"); } @Test public void testSqsClientSpanLegacyFormatUrls() { - validate("https://ap-northeast-2.queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); - validate("http://cn-northwest-1.queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); - validate("http://cn-north-1.queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); - validate( + validateGetQueueName( + "https://ap-northeast-2.queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); + validateGetQueueName( + "http://cn-northwest-1.queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); + validateGetQueueName("http://cn-north-1.queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); + validateGetQueueName( "ap-south-1.queue.amazonaws.com/123412341234/MyLongerQueueNameHere", "MyLongerQueueNameHere"); - validate("https://queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); + validateGetQueueName("https://queue.amazonaws.com/123456789012/MyQueue", "MyQueue"); } @Test public void testSqsClientSpanCustomUrls() { - validate("http://127.0.0.1:1212/123456789012/MyQueue", "MyQueue"); - validate("https://127.0.0.1:1212/123412341234/RRR", "RRR"); - validate("127.0.0.1:1212/123412341234/QQ", "QQ"); - validate("https://amazon.com/123412341234/BB", "BB"); + validateGetQueueName("http://127.0.0.1:1212/123456789012/MyQueue", "MyQueue"); + validateGetQueueName("https://127.0.0.1:1212/123412341234/RRR", "RRR"); + validateGetQueueName("127.0.0.1:1212/123412341234/QQ", "QQ"); + validateGetQueueName("https://amazon.com/123412341234/BB", "BB"); } @Test public void testSqsClientSpanLongUrls() { String queueName = "a".repeat(80); - validate("http://127.0.0.1:1212/123456789012/" + queueName, queueName); + validateGetQueueName("http://127.0.0.1:1212/123456789012/" + queueName, queueName); String queueNameTooLong = "a".repeat(81); - validate("http://127.0.0.1:1212/123456789012/" + queueNameTooLong, null); + validateGetQueueName("http://127.0.0.1:1212/123456789012/" + queueNameTooLong, null); } @Test public void testClientSpanSqsInvalidOrEmptyUrls() { - validate(null, null); - validate("", null); - validate(" ", null); - validate("/", null); - validate("//", null); - validate("///", null); - validate("//asdf", null); - validate("/123412341234/as&df", null); - validate("invalidUrl", null); - validate("https://www.amazon.com", null); - validate("https://sqs.us-east-1.amazonaws.com/123412341234/.", null); - validate("https://sqs.us-east-1.amazonaws.com/12/Queue", null); - validate("https://sqs.us-east-1.amazonaws.com/A/A", null); - validate("https://sqs.us-east-1.amazonaws.com/123412341234/A/ThisShouldNotBeHere", null); + validateGetQueueName(null, null); + validateGetQueueName("", null); + validateGetQueueName(" ", null); + validateGetQueueName("/", null); + validateGetQueueName("//", null); + validateGetQueueName("///", null); + validateGetQueueName("//asdf", null); + validateGetQueueName("/123412341234/as&df", null); + validateGetQueueName("invalidUrl", null); + validateGetQueueName("https://www.amazon.com", null); + validateGetQueueName("https://sqs.us-east-1.amazonaws.com/123412341234/.", null); + validateGetQueueName("https://sqs.us-east-1.amazonaws.com/12xxxxxxxxxx/Queue", null); + validateGetQueueName("https://sqs.us-east-1.amazonaws.com/A/A", null); + validateGetQueueName( + "https://sqs.us-east-1.amazonaws.com/123412341234/A/ThisShouldNotBeHere", null); } - private void validate(String url, String expectedName) { + @Test + public void testClientSpanSqsAccountId() { + validateGetAccountId(null, null); + validateGetAccountId("", null); + validateGetAccountId(" ", null); + validateGetAccountId("/", null); + validateGetAccountId("//", null); + validateGetAccountId("///", null); + validateGetAccountId("//asdf", null); + validateGetAccountId("/123412341234/as&df", null); + validateGetAccountId("invalidUrl", null); + validateGetAccountId("https://www.amazon.com", null); + validateGetAccountId("https://sqs.us-east-1.amazonaws.com/123412341234/Queue", "123412341234"); + validateGetAccountId("https://sqs.us-east-1.amazonaws.com/12341234/Queue", "12341234"); + validateGetAccountId("https://sqs.us-east-1.amazonaws.com/1234123412xx/Queue", null); + validateGetAccountId("https://sqs.us-east-1.amazonaws.com/1234123412xx", null); + } + + @Test + public void testClientSpanSqsRegion() { + validateGetRegion(null, null); + validateGetRegion("", null); + validateGetRegion(" ", null); + validateGetRegion("/", null); + validateGetRegion("//", null); + validateGetRegion("///", null); + validateGetRegion("//asdf", null); + validateGetRegion("/123412341234/as&df", null); + validateGetRegion("invalidUrl", null); + validateGetRegion("https://www.amazon.com", null); + validateGetRegion("https://sqs.us-east-1.amazonaws.com/123412341234/Queue", "us-east-1"); + } + + private void validateGetRegion(String url, String expectedRegion) { + assertThat(SqsUrlParser.getRegion(url)).isEqualTo(Optional.ofNullable(expectedRegion)); + } + + private void validateGetAccountId(String url, String expectedAccountId) { + assertThat(SqsUrlParser.getAccountId(url)).isEqualTo(Optional.ofNullable(expectedAccountId)); + } + + private void validateGetQueueName(String url, String expectedName) { assertThat(SqsUrlParser.getQueueName(url)).isEqualTo(Optional.ofNullable(expectedName)); } } diff --git a/build.gradle.kts b/build.gradle.kts index 973d7d73ba..843124bd80 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,8 +40,8 @@ nebulaRelease { nexusPublishing { repositories { sonatype { - nexusUrl.set(uri("https://aws.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://aws.oss.sonatype.org/content/repositories/snapshots/")) + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) username.set(System.getenv("PUBLISH_TOKEN_USERNAME")) password.set(System.getenv("PUBLISH_TOKEN_PASSWORD")) } diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 00aa199d82..55ed00a16c 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,7 +27,7 @@ data class DependencySet(val group: String, val version: String, val modules: Li val testSnapshots = rootProject.findProperty("testUpstreamSnapshots") == "true" // This is the version of the upstream instrumentation BOM -val otelVersion = "2.11.0-adot2" +val otelVersion = "2.11.0-adot3" val otelSnapshotVersion = "2.12.0" val otelAlphaVersion = if (!testSnapshots) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" val otelJavaAgentVersion = if (!testSnapshots) otelVersion else "$otelSnapshotVersion-SNAPSHOT" @@ -45,7 +45,7 @@ val dependencyBoms = listOf( "org.junit:junit-bom:5.10.1", "org.springframework.boot:spring-boot-dependencies:2.7.17", "org.testcontainers:testcontainers-bom:1.19.3", - "software.amazon.awssdk:bom:2.21.33", + "software.amazon.awssdk:bom:2.30.17", ) val dependencySets = listOf( diff --git a/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/build.gradle.kts b/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/build.gradle.kts index 9aec0ccbed..f638f1965b 100644 --- a/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/build.gradle.kts +++ b/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/build.gradle.kts @@ -101,8 +101,8 @@ tasks.create("printVersion") { nexusPublishing { repositories { sonatype { - nexusUrl.set(uri("https://aws.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://aws.oss.sonatype.org/content/repositories/snapshots/")) + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) username.set(System.getenv("PUBLISH_TOKEN_USERNAME")) password.set(System.getenv("PUBLISH_TOKEN_PASSWORD")) } diff --git a/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/settings.gradle.kts b/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/settings.gradle.kts index 33986beb6b..9cb79500f1 100644 --- a/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/settings.gradle.kts +++ b/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/settings.gradle.kts @@ -6,7 +6,7 @@ dependencyResolutionManagement { mavenLocal() maven { - setUrl("https://oss.sonatype.org/content/repositories/snapshots") + setUrl("https://central.sonatype.com/repository/maven-snapshots/") } } } @@ -16,4 +16,4 @@ pluginManagement { id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("nebula.release") version "18.0.6" } -} \ No newline at end of file +} diff --git a/lambda-layer/patches/aws-otel-java-instrumentation.patch b/lambda-layer/patches/aws-otel-java-instrumentation.patch index 99dd328af1..2c6dfa70dd 100644 --- a/lambda-layer/patches/aws-otel-java-instrumentation.patch +++ b/lambda-layer/patches/aws-otel-java-instrumentation.patch @@ -6,7 +6,7 @@ index 9493189..6090207 100644 val TEST_SNAPSHOTS = rootProject.findProperty("testUpstreamSnapshots") == "true" // This is the version of the upstream instrumentation BOM --val otelVersion = "2.11.0-adot2" +-val otelVersion = "2.11.0-adot3" +val otelVersion = "2.11.0-adot-lambda1" val otelSnapshotVersion = "2.12.0" val otelAlphaVersion = if (!TEST_SNAPSHOTS) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" diff --git a/lambda-layer/patches/opentelemetry-java-instrumentation.patch b/lambda-layer/patches/opentelemetry-java-instrumentation.patch index e9253c5d87..127751f5b0 100644 --- a/lambda-layer/patches/opentelemetry-java-instrumentation.patch +++ b/lambda-layer/patches/opentelemetry-java-instrumentation.patch @@ -310,8 +310,8 @@ index 7900c9a4d9..80383d7c22 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ --val stableVersion = "2.11.0-adot2" --val alphaVersion = "2.11.0-adot2-alpha" +-val stableVersion = "2.11.0-adot3" +-val alphaVersion = "2.11.0-adot3-alpha" +val stableVersion = "2.11.0-adot-lambda1" +val alphaVersion = "2.11.0-adot-lambda1-alpha" diff --git a/settings.gradle.kts b/settings.gradle.kts index f6b5033352..1a76fa3e2c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,7 +33,7 @@ dependencyResolutionManagement { mavenLocal() maven { - setUrl("https://oss.sonatype.org/content/repositories/snapshots") + setUrl("https://central.sonatype.com/repository/maven-snapshots/") } } }