diff --git a/awsagentprovider/build.gradle.kts b/awsagentprovider/build.gradle.kts index 5b99011ab7..3e92ad8b1a 100644 --- a/awsagentprovider/build.gradle.kts +++ b/awsagentprovider/build.gradle.kts @@ -36,6 +36,8 @@ dependencies { implementation("io.opentelemetry.contrib:opentelemetry-aws-resources") // Json file reader implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1") + // Import AWS SDK v1 core for ARN parsing utilities + implementation("com.amazonaws:aws-java-sdk-core:1.12.773") // Export configuration compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") 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 457e81d77d..ef2999a28a 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 @@ -38,6 +38,9 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_REMOTE_RESOURCE_IDENTIFIER = AttributeKey.stringKey("aws.remote.resource.identifier"); + static final AttributeKey AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER = + AttributeKey.stringKey("aws.remote.resource.cfn.primary.identifier"); + static final AttributeKey AWS_REMOTE_RESOURCE_TYPE = AttributeKey.stringKey("aws.remote.resource.type"); @@ -50,6 +53,26 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_CONSUMER_PARENT_SPAN_KIND = AttributeKey.stringKey("aws.consumer.parent.span.kind"); + static final AttributeKey AWS_STATE_MACHINE_ARN = + AttributeKey.stringKey("aws.stepfunctions.state_machine.arn"); + + static final AttributeKey AWS_STEP_FUNCTIONS_ACTIVITY_ARN = + AttributeKey.stringKey("aws.stepfunctions.activity.arn"); + + static final AttributeKey AWS_SNS_TOPIC_ARN = AttributeKey.stringKey("aws.sns.topic.arn"); + + static final AttributeKey AWS_SECRET_ARN = + AttributeKey.stringKey("aws.secretsmanager.secret.arn"); + + static final AttributeKey AWS_LAMBDA_NAME = + AttributeKey.stringKey("aws.lambda.function.name"); + + static final AttributeKey AWS_LAMBDA_ARN = + AttributeKey.stringKey("aws.lambda.function.arn"); + + static final AttributeKey AWS_LAMBDA_RESOURCE_ID = + AttributeKey.stringKey("aws.lambda.resource_mapping.id"); + // use the same AWS Resource attribute name defined by OTel java auto-instr for aws_sdk_v_1_1 // TODO: all AWS specific attributes should be defined in semconv package and reused cross all // otel packages. Related sim - 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 68ba1bc824..63c806ea98 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 @@ -42,9 +42,12 @@ import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID; 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_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID; +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; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME; @@ -54,7 +57,11 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER; 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; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SNS_TOPIC_ARN; 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_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL; @@ -67,6 +74,7 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isDBSpan; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent; +import com.amazonaws.arn.Arn; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -111,6 +119,10 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator { private static final String NORMALIZED_SQS_SERVICE_NAME = "AWS::SQS"; private static final String NORMALIZED_BEDROCK_SERVICE_NAME = "AWS::Bedrock"; private static final String NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME = "AWS::BedrockRuntime"; + private static final String NORMALIZED_STEPFUNCTIONS_SERVICE_NAME = "AWS::StepFunctions"; + private static final String NORMALIZED_SNS_SERVICE_NAME = "AWS::SNS"; + private static final String NORMALIZED_SECRETSMANAGER_SERVICE_NAME = "AWS::SecretsManager"; + private static final String NORMALIZED_LAMBDA_SERVICE_NAME = "AWS::Lambda"; // Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present. private static final String GRAPHQL = "graphql"; @@ -371,6 +383,18 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa case "AmazonBedrockRuntime": // AWS SDK v1 case "BedrockRuntime": // AWS SDK v2 return NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME; + case "AWSStepFunctions": // AWS SDK v1 + case "Sfn": // AWS SDK v2 + return NORMALIZED_STEPFUNCTIONS_SERVICE_NAME; + case "AmazonSNS": + case "Sns": + return NORMALIZED_SNS_SERVICE_NAME; + case "AWSSecretsManager": // AWS SDK v1 + case "SecretsManager": // AWS SDK v2 + return NORMALIZED_SECRETSMANAGER_SERVICE_NAME; + case "AWSLambda": // AWS SDK v1 + case "Lambda": // AWS SDK v2 + return NORMALIZED_LAMBDA_SERVICE_NAME; default: return "AWS::" + serviceName; } @@ -392,6 +416,7 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa private static void setRemoteResourceTypeAndIdentifier(SpanData span, AttributesBuilder builder) { Optional remoteResourceType = Optional.empty(); Optional remoteResourceIdentifier = Optional.empty(); + Optional cloudformationPrimaryIdentifier = Optional.empty(); if (isAwsSDKSpan(span)) { if (isKeyPresent(span, AWS_TABLE_NAME)) { @@ -434,18 +459,88 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes remoteResourceType = Optional.of(NORMALIZED_BEDROCK_SERVICE_NAME + "::Model"); remoteResourceIdentifier = Optional.ofNullable(escapeDelimiters(span.getAttributes().get(GEN_AI_REQUEST_MODEL))); + } else if (isKeyPresent(span, AWS_STATE_MACHINE_ARN)) { + remoteResourceType = Optional.of(NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + "::StateMachine"); + remoteResourceIdentifier = + getSfnResourceNameFromArn( + Optional.ofNullable( + escapeDelimiters(span.getAttributes().get(AWS_STATE_MACHINE_ARN)))); + cloudformationPrimaryIdentifier = + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_STATE_MACHINE_ARN))); + } else if (isKeyPresent(span, AWS_STEP_FUNCTIONS_ACTIVITY_ARN)) { + remoteResourceType = Optional.of(NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + "::Activity"); + remoteResourceIdentifier = + getSfnResourceNameFromArn( + Optional.ofNullable( + escapeDelimiters(span.getAttributes().get(AWS_STEP_FUNCTIONS_ACTIVITY_ARN)))); + cloudformationPrimaryIdentifier = + Optional.ofNullable( + escapeDelimiters(span.getAttributes().get(AWS_STEP_FUNCTIONS_ACTIVITY_ARN))); + } else if (isKeyPresent(span, AWS_SNS_TOPIC_ARN)) { + remoteResourceType = Optional.of(NORMALIZED_SNS_SERVICE_NAME + "::Topic"); + remoteResourceIdentifier = + getSnsResourceNameFromArn( + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SNS_TOPIC_ARN)))); + cloudformationPrimaryIdentifier = + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SNS_TOPIC_ARN))); + } else if (isKeyPresent(span, AWS_SECRET_ARN)) { + remoteResourceType = Optional.of(NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret"); + remoteResourceIdentifier = + getSecretsManagerResourceNameFromArn( + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN)))); + cloudformationPrimaryIdentifier = + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN))); + } else if (isKeyPresent(span, AWS_LAMBDA_NAME)) { + remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::Function"); + remoteResourceIdentifier = + getLambdaResourceNameFromAribitraryName( + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME)))); + } else if (isKeyPresent(span, AWS_LAMBDA_RESOURCE_ID)) { + remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::EventSourceMapping"); + remoteResourceIdentifier = + Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_RESOURCE_ID))); } } else if (isDBSpan(span)) { remoteResourceType = Optional.of(DB_CONNECTION_RESOURCE_TYPE); remoteResourceIdentifier = getDbConnection(span); } + if (cloudformationPrimaryIdentifier.isEmpty()) { + cloudformationPrimaryIdentifier = remoteResourceIdentifier; + } + if (remoteResourceType.isPresent() && remoteResourceIdentifier.isPresent()) { builder.put(AWS_REMOTE_RESOURCE_TYPE, remoteResourceType.get()); builder.put(AWS_REMOTE_RESOURCE_IDENTIFIER, remoteResourceIdentifier.get()); + builder.put(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformationPrimaryIdentifier.get()); } } + // NOTE: "name" in this case can be either the lambda name or lambda arn + private static Optional getLambdaResourceNameFromAribitraryName( + Optional arbitraryName) { + if (arbitraryName != null && arbitraryName.get().startsWith("arn:aws:lambda:")) { + Arn resourceArn = Arn.fromString(arbitraryName.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } + return arbitraryName; + } + + private static Optional getSecretsManagerResourceNameFromArn(Optional stringArn) { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } + + private static Optional getSfnResourceNameFromArn(Optional stringArn) { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString().split(":")[1]); + } + + private static Optional getSnsResourceNameFromArn(Optional stringArn) { + Arn resourceArn = Arn.fromString(stringArn.get()); + return Optional.of(resourceArn.getResource().toString()); + } + /** * RemoteResourceIdentifier is populated with rule * ^[{db.name}|]?{address}[|{port}]? 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 594d3f7dc9..c51188a6e8 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 @@ -26,6 +26,8 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID; 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_NAME; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME; @@ -35,7 +37,11 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER; 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; +import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SNS_TOPIC_ARN; 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_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL; @@ -770,6 +776,42 @@ public void testSdkClientSpanWithRemoteResourceAttributes() { mockAttribute(GEN_AI_REQUEST_MODEL, "test.service_^id"); validateRemoteResourceAttributes("AWS::Bedrock::Model", "test.service_^^id"); 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"); + validateRemoteResourceAttributes("AWS::StepFunctions::StateMachine", "test_state_machine"); + mockAttribute(AWS_STATE_MACHINE_ARN, null); + + // 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"); + validateRemoteResourceAttributes("AWS::StepFunctions::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"); + validateRemoteResourceAttributes("AWS::SNS::Topic", "testTopic"); + mockAttribute(AWS_SNS_TOPIC_ARN, null); + + // Validate behaviour of AWS_SECRET_ARN, then remove it. + mockAttribute( + AWS_SECRET_ARN, "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretName"); + validateRemoteResourceAttributes("AWS::SecretsManager::Secret", "secretName"); + mockAttribute(AWS_SECRET_ARN, null); + + // Validate behaviour of AWS_LAMBDA_NAME, then remove it. + mockAttribute(AWS_LAMBDA_NAME, "arn:aws:lambda:us-east-1:123456789012:function:functionName"); + validateRemoteResourceAttributes("AWS::Lambda::Function", "functionName"); + mockAttribute(AWS_LAMBDA_NAME, null); + + // Validate behaviour of AWS_LAMBDA_RESOURCE_ID + mockAttribute(AWS_LAMBDA_RESOURCE_ID, "eventSourceId"); + validateRemoteResourceAttributes("AWS::Lambda::EventSourceMapping", "eventSourceId"); + mockAttribute(AWS_LAMBDA_RESOURCE_ID, null); + mockAttribute(RPC_SYSTEM, "null"); } @@ -1175,6 +1217,7 @@ public void testNormalizeRemoteServiceName_AwsSdk() { testAwsSdkServiceNormalization("AWSBedrockAgentRuntime", "AWS::Bedrock"); testAwsSdkServiceNormalization("AWSBedrockAgent", "AWS::Bedrock"); testAwsSdkServiceNormalization("AmazonBedrockRuntime", "AWS::BedrockRuntime"); + testAwsSdkServiceNormalization("AWSStepFunctions", "AWS::StepFunctions"); // AWS SDK V2 testAwsSdkServiceNormalization("DynamoDb", "AWS::DynamoDB");