From 1ba66c989717c871129a76c679f641cc5469c998 Mon Sep 17 00:00:00 2001 From: Michael He Date: Fri, 6 Jun 2025 17:20:52 +0000 Subject: [PATCH 01/13] refactor lambda support changes --- .../distro/_aws_metric_attribute_generator.py | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py index 5610539a3..77a85ebc1 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py @@ -103,6 +103,9 @@ _NORMALIZED_LAMBDA_SERVICE_NAME: str = "AWS::Lambda" _DB_CONNECTION_STRING_TYPE: str = "DB::Connection" +_LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT: str = "LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT" +_LAMBDA_INVOKE_OPERATION: str = "Invoke" + # Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present. _GRAPHQL: str = "graphql" @@ -145,6 +148,7 @@ def _generate_dependency_metric_attributes(span: ReadableSpan, resource: Resourc _set_egress_operation(span, attributes) _set_remote_service_and_operation(span, attributes) _set_remote_type_and_identifier(span, attributes) + _set_remote_environment(span, attributes) _set_remote_db_user(span, attributes) _set_span_kind_for_dependency(span, attributes) return attributes @@ -317,7 +321,19 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str "Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME, "SNS": _NORMALIZED_SNS_SERVICE_NAME, "SFN": _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME, + "Lambda": _NORMALIZED_LAMBDA_SERVICE_NAME, } + + # Special handling for Lambda invoke operations + if _is_lambda_invoke_operation(span): + # AWS_LAMBDA_FUNCTION_NAME is guaranteed to contain function name (not ARN) + # due to logic in Lambda botocore patches during instrumentation + lambda_function_name = span.attributes.get(AWS_LAMBDA_FUNCTION_NAME) + # If Lambda name is not present, use UnknownRemoteService + # This is intentional - we want to clearly indicate when the Lambda function name + # is missing rather than falling back to a generic service name + return lambda_function_name if lambda_function_name else UNKNOWN_REMOTE_SERVICE + return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name) return service_name @@ -450,23 +466,12 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri )[-1] cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_STEPFUNCTIONS_ACTIVITY_ARN)) elif is_key_present(span, AWS_LAMBDA_FUNCTION_NAME): - # Handling downstream Lambda as a service vs. an AWS resource: - # - If the method call is "Invoke", we treat downstream Lambda as a service. - # - Otherwise, we treat it as an AWS resource. - # - # This addresses a Lambda topology issue in Application Signals. - # More context in PR: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319 - # - # NOTE: The env var LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was introduced as part of this fix. - # It is optional and allows users to override the default value if needed. - if span.attributes.get(_RPC_METHOD) == "Invoke": - attributes[AWS_REMOTE_SERVICE] = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_NAME)) - - attributes[AWS_REMOTE_ENVIRONMENT] = ( - f'lambda:{os.environ.get("LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT", "default")}' - ) - else: + # For non-Invoke Lambda operations, treat Lambda as a resource, + # see normalize_remote_service_name for more information. + if not _is_lambda_invoke_operation(span): remote_resource_type = _NORMALIZED_LAMBDA_SERVICE_NAME + "::Function" + # AWS_LAMBDA_FUNCTION_NAME is guaranteed to contain function name (not ARN) + # due to logic in Lambda botocore patches during instrumentation remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_NAME)) cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_ARN)) elif is_key_present(span, AWS_LAMBDA_RESOURCEMAPPING_ID): @@ -491,6 +496,28 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri attributes[AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudformation_primary_identifier +def _set_remote_environment(span: ReadableSpan, attributes: BoundedAttributes) -> None: + # We want to treat downstream Lambdas as a service rather than a resource because + # Application Signals topology map gets disconnected due to conflicting Lambda Entity + # definitions + # Additional context can be found in + # https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319 + if _is_lambda_invoke_operation(span): + remote_environment = os.environ.get(_LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT, "").strip() + if not remote_environment: + remote_environment = "default" + attributes[AWS_REMOTE_ENVIRONMENT] = f"lambda:{remote_environment}" + + +def _is_lambda_invoke_operation(span: ReadableSpan) -> bool: + """Check if the span represents a Lambda Invoke operation.""" + if not is_aws_sdk_span(span): + return False + + rpc_service = _get_remote_service(span, _RPC_SERVICE) + return rpc_service == "Lambda" and span.attributes.get(_RPC_METHOD) == _LAMBDA_INVOKE_OPERATION + + def _get_db_connection(span: ReadableSpan) -> None: """ RemoteResourceIdentifier is populated with rule: From a89161a5ec102e0b841fdb512d915067f84c06dc Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 15:35:01 +0000 Subject: [PATCH 02/13] add cfn primary id assertions in unit test cases --- .../test_aws_metric_attribute_generator.py | 97 ++++++++++++++----- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index 6e4e76cc5..bea30fc6d 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -11,6 +11,7 @@ from amazon.opentelemetry.distro._aws_attribute_keys import ( AWS_BEDROCK_AGENT_ID, AWS_BEDROCK_DATA_SOURCE_ID, + AWS_BEDROCK_GUARDRAIL_ARN, AWS_BEDROCK_GUARDRAIL_ID, AWS_BEDROCK_KNOWLEDGE_BASE_ID, AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, @@ -1006,12 +1007,14 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::SQS::Queue", "aws_queue_name") + self._validate_remote_resource_attributes( + "AWS::SQS::Queue", "aws_queue_name", "https://sqs.us-east-2.amazonaws.com/123456789012/Queue" + ) self._mock_attribute([AWS_SQS_QUEUE_URL, AWS_SQS_QUEUE_NAME], [None, None]) # Valid queue name with invalid queue URL, we should default to using the queue name. self._mock_attribute([AWS_SQS_QUEUE_URL, AWS_SQS_QUEUE_NAME], ["invalidUrl", "aws_queue_name"], keys, values) - self._validate_remote_resource_attributes("AWS::SQS::Queue", "aws_queue_name") + self._validate_remote_resource_attributes("AWS::SQS::Queue", "aws_queue_name", "invalidUrl") self._mock_attribute([AWS_SQS_QUEUE_URL, AWS_SQS_QUEUE_NAME], [None, None]) # Validate behaviour of AWS_KINESIS_STREAM_NAME attribute, then remove it. @@ -1063,7 +1066,9 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::Bedrock::DataSource", "test_datasource_id") + self._validate_remote_resource_attributes( + "AWS::Bedrock::DataSource", "test_datasource_id", "test_knowledge_base_id|test_datasource_id" + ) self._mock_attribute([AWS_BEDROCK_DATA_SOURCE_ID, AWS_BEDROCK_KNOWLEDGE_BASE_ID], [None, None]) # Validate behaviour of AWS_BEDROCK_DATA_SOURCE_ID attribute with special chars(^), then remove it. @@ -1073,18 +1078,38 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::Bedrock::DataSource", "test_datasource_^^id") + self._validate_remote_resource_attributes( + "AWS::Bedrock::DataSource", "test_datasource_^^id", "test_knowledge_base_^^id|test_datasource_^^id" + ) self._mock_attribute([AWS_BEDROCK_DATA_SOURCE_ID, AWS_BEDROCK_KNOWLEDGE_BASE_ID], [None, None]) # Validate behaviour of AWS_BEDROCK_GUARDRAIL_ID attribute, then remove it. - self._mock_attribute([AWS_BEDROCK_GUARDRAIL_ID], ["test_guardrail_id"], keys, values) - self._validate_remote_resource_attributes("AWS::Bedrock::Guardrail", "test_guardrail_id") - self._mock_attribute([AWS_BEDROCK_GUARDRAIL_ID], [None]) + self._mock_attribute( + [AWS_BEDROCK_GUARDRAIL_ID, AWS_BEDROCK_GUARDRAIL_ARN], + ["test_guardrail_id", "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_id"], + keys, + values, + ) + self._validate_remote_resource_attributes( + "AWS::Bedrock::Guardrail", + "test_guardrail_id", + "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_id", + ) + self._mock_attribute([AWS_BEDROCK_GUARDRAIL_ID, AWS_BEDROCK_GUARDRAIL_ARN], [None, None]) # Validate behaviour of AWS_BEDROCK_GUARDRAIL_ID attribute with special chars(^), then remove it. - self._mock_attribute([AWS_BEDROCK_GUARDRAIL_ID], ["test_guardrail_^id"], keys, values) - self._validate_remote_resource_attributes("AWS::Bedrock::Guardrail", "test_guardrail_^^id") - self._mock_attribute([AWS_BEDROCK_GUARDRAIL_ID], [None]) + self._mock_attribute( + [AWS_BEDROCK_GUARDRAIL_ID, AWS_BEDROCK_GUARDRAIL_ARN], + ["test_guardrail_^id", "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_^id"], + keys, + values, + ) + self._validate_remote_resource_attributes( + "AWS::Bedrock::Guardrail", + "test_guardrail_^^id", + "arn:aws:bedrock:us-east-1:123456789012:guardrail/test_guardrail_^^id", + ) + self._mock_attribute([AWS_BEDROCK_GUARDRAIL_ID, AWS_BEDROCK_GUARDRAIL_ARN], [None, None]) # Validate behaviour of AWS_BEDROCK_KNOWLEDGE_BASE_ID attribute, then remove it. self._mock_attribute([AWS_BEDROCK_KNOWLEDGE_BASE_ID], ["test_knowledgeBase_id"], keys, values) @@ -1113,12 +1138,18 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::SecretsManager::Secret", "secret_name-lERW9H") + self._validate_remote_resource_attributes( + "AWS::SecretsManager::Secret", + "secret_name-lERW9H", + "arn:aws:secretsmanager:us-east-1:123456789012:secret:secret_name-lERW9H", + ) self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None]) # Validate behaviour of AWS_SNS_TOPIC_ARN attribute, then remove it. self._mock_attribute([AWS_SNS_TOPIC_ARN], ["arn:aws:sns:us-west-2:012345678901:test_topic"], keys, values) - self._validate_remote_resource_attributes("AWS::SNS::Topic", "test_topic") + self._validate_remote_resource_attributes( + "AWS::SNS::Topic", "test_topic", "arn:aws:sns:us-west-2:012345678901:test_topic" + ) self._mock_attribute([AWS_SNS_TOPIC_ARN], [None]) # Validate behaviour of AWS_STEPFUNCTIONS_STATEMACHINE_ARN attribute, then remove it. @@ -1128,7 +1159,11 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::StepFunctions::StateMachine", "test_state_machine") + self._validate_remote_resource_attributes( + "AWS::StepFunctions::StateMachine", + "test_state_machine", + "arn:aws:states:us-east-1:123456789012:stateMachine:test_state_machine", + ) self._mock_attribute([AWS_STEPFUNCTIONS_STATEMACHINE_ARN], [None]) # Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN attribute, then remove it. @@ -1138,7 +1173,11 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::StepFunctions::Activity", "testActivity") + self._validate_remote_resource_attributes( + "AWS::StepFunctions::Activity", + "testActivity", + "arn:aws:states:us-east-1:007003123456789012:activity:testActivity", + ) self._mock_attribute([AWS_STEPFUNCTIONS_ACTIVITY_ARN], [None]) # Validate behaviour of AWS_LAMBDA_RESOURCEMAPPING_ID attribute, then remove it. @@ -1160,8 +1199,8 @@ def test_sdk_client_span_with_remote_resource_attributes(self): # Test AWS Lambda Invoke scenario with default lambda remote environment self.span_mock.kind = SpanKind.CLIENT self._mock_attribute( - [AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD], - ["test_downstream_lambda1", "Invoke"], + [AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD, SpanAttributes.RPC_SERVICE], + ["test_downstream_lambda1", "Invoke", "Lambda"], keys, values, ) @@ -1173,14 +1212,16 @@ def test_sdk_client_span_with_remote_resource_attributes(self): self.assertNotIn(AWS_REMOTE_RESOURCE_TYPE, dependency_attributes) self.assertNotIn(AWS_REMOTE_RESOURCE_IDENTIFIER, dependency_attributes) self.assertNotIn(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, dependency_attributes) - self._mock_attribute([AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD], [None, None]) + self._mock_attribute( + [AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD, SpanAttributes.RPC_SERVICE], [None, None, None] + ) # Test AWS Lambda Invoke scenario with user-configured lambda remote environment os.environ["LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT"] = "test" self.span_mock.kind = SpanKind.CLIENT self._mock_attribute( - [AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD], - ["testLambdaFunction", "Invoke"], + [AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD, SpanAttributes.RPC_SERVICE], + ["testLambdaFunction", "Invoke", "Lambda"], keys, values, ) @@ -1192,7 +1233,9 @@ def test_sdk_client_span_with_remote_resource_attributes(self): self.assertNotIn(AWS_REMOTE_RESOURCE_TYPE, dependency_attributes) self.assertNotIn(AWS_REMOTE_RESOURCE_IDENTIFIER, dependency_attributes) self.assertNotIn(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, dependency_attributes) - self._mock_attribute([AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD], [None, None]) + self._mock_attribute( + [AWS_LAMBDA_FUNCTION_NAME, SpanAttributes.RPC_METHOD, SpanAttributes.RPC_SERVICE], [None, None, None] + ) os.environ.pop("LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT", None) # Test AWS Lambda non-Invoke scenario @@ -1204,7 +1247,7 @@ def test_sdk_client_span_with_remote_resource_attributes(self): keys, values, ) - self._validate_remote_resource_attributes("AWS::Lambda::Function", "testLambdaFunction") + self._validate_remote_resource_attributes("AWS::Lambda::Function", "testLambdaFunction", lambda_arn) self._mock_attribute( [AWS_LAMBDA_FUNCTION_NAME, AWS_LAMBDA_FUNCTION_ARN, SpanAttributes.RPC_METHOD], [None, None, None] ) @@ -1487,7 +1530,13 @@ def test_client_db_span_with_remote_resource_attributes(self): [None], ) - def _validate_remote_resource_attributes(self, expected_type: str, expected_identifier: str) -> None: + def _validate_remote_resource_attributes( + self, expected_type: str, expected_identifier: str, expected_cfn_primary_id: str = None + ) -> None: + # If expected_cfn_primary_id is not provided, it defaults to expected_identifier + if expected_cfn_primary_id is None: + expected_cfn_primary_id = expected_identifier + # Client, Producer, and Consumer spans should generate the expected remote resource attribute self.span_mock.kind = SpanKind.CLIENT actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( @@ -1495,6 +1544,7 @@ def _validate_remote_resource_attributes(self, expected_type: str, expected_iden ) self.assertEqual(expected_type, actual_attributes.get(AWS_REMOTE_RESOURCE_TYPE)) self.assertEqual(expected_identifier, actual_attributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)) + self.assertEqual(expected_cfn_primary_id, actual_attributes.get(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER)) self.span_mock.kind = SpanKind.PRODUCER actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( @@ -1502,6 +1552,7 @@ def _validate_remote_resource_attributes(self, expected_type: str, expected_iden ) self.assertEqual(expected_type, actual_attributes.get(AWS_REMOTE_RESOURCE_TYPE)) self.assertEqual(expected_identifier, actual_attributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)) + self.assertEqual(expected_cfn_primary_id, actual_attributes.get(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER)) self.span_mock.kind = SpanKind.CONSUMER actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( @@ -1509,6 +1560,7 @@ def _validate_remote_resource_attributes(self, expected_type: str, expected_iden ) self.assertEqual(expected_type, actual_attributes.get(AWS_REMOTE_RESOURCE_TYPE)) self.assertEqual(expected_identifier, actual_attributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)) + self.assertEqual(expected_cfn_primary_id, actual_attributes.get(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER)) # Server span should not generate remote resource attribute self.span_mock.kind = SpanKind.SERVER @@ -1517,6 +1569,7 @@ def _validate_remote_resource_attributes(self, expected_type: str, expected_iden ) self.assertNotIn(AWS_REMOTE_RESOURCE_TYPE, actual_attributes) self.assertNotIn(AWS_REMOTE_RESOURCE_IDENTIFIER, actual_attributes) + self.assertNotIn(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, actual_attributes) self._mock_attribute([SpanAttributes.DB_SYSTEM], [None]) From fc6a5fa6750e82064ec3857b54f257d98c9151e8 Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 15:54:44 +0000 Subject: [PATCH 03/13] add set remote environment unit test case --- .../test_aws_metric_attribute_generator.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index bea30fc6d..36373d4da 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -1592,3 +1592,86 @@ def _validate_attributes_produced_for_non_local_root_span_of_kind( self.assertIsNone(dependency_attributes) self.assertEqual(len(service_attributes), len(BoundedAttributes(attributes=expected_attributes))) self.assertEqual(service_attributes, BoundedAttributes(attributes=expected_attributes)) + + def test_set_remote_environment(self): + """Test remote environment setting for Lambda invoke operations.""" + keys = [] + values = [] + + # Test 1: Setting remote environment when all relevant attributes are present + self.span_mock.kind = SpanKind.CLIENT + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + ], + ["aws-api", "Lambda", "Invoke", "testFunction"], + keys, + values, + ) + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertEqual(actual_attributes.get(AWS_REMOTE_ENVIRONMENT), "lambda:default") + + # Test 2: NOT setting it when RPC_SYSTEM is missing + self._mock_attribute([SpanAttributes.RPC_SYSTEM], [None]) + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertIsNone(actual_attributes.get(AWS_REMOTE_ENVIRONMENT)) + self._mock_attribute([SpanAttributes.RPC_SYSTEM], ["aws-api"], keys, values) + + # Test 3: NOT setting it when RPC_METHOD is missing + self._mock_attribute([SpanAttributes.RPC_METHOD], [None]) + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertIsNone(actual_attributes.get(AWS_REMOTE_ENVIRONMENT)) + self._mock_attribute([SpanAttributes.RPC_METHOD], ["Invoke"], keys, values) + + # Test 4: Still setting it to lambda:default when AWS_LAMBDA_FUNCTION_NAME is missing + # Keep the other attributes but remove AWS_LAMBDA_FUNCTION_NAME + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + ], + ["aws-api", "Lambda", "Invoke", None], + keys, + values, + ) + + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertEqual(actual_attributes.get(AWS_REMOTE_ENVIRONMENT), "lambda:default") + self._mock_attribute([AWS_LAMBDA_FUNCTION_NAME], ["testFunction"], keys, values) + + # Test 5: NOT setting it for non-Lambda services + self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.RPC_METHOD], + ["S3", "GetObject"], + keys, + values, + ) + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertIsNone(actual_attributes.get(AWS_REMOTE_ENVIRONMENT)) + + # Test 6: NOT setting it for Lambda non-Invoke operations + self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.RPC_METHOD], + ["Lambda", "GetFunction"], + keys, + values, + ) + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertIsNone(actual_attributes.get(AWS_REMOTE_ENVIRONMENT)) From 133d544cb91c8cbfd3931f9890c7271edd1aff8a Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 16:20:41 +0000 Subject: [PATCH 04/13] add remote service normalization test logic for lambda --- .../test_aws_metric_attribute_generator.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index 36373d4da..6cd842122 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -892,6 +892,40 @@ def test_normalize_remote_service_name_aws_sdk(self): self.validate_aws_sdk_service_normalization("SNS", "AWS::SNS") self.validate_aws_sdk_service_normalization("SFN", "AWS::StepFunctions") + # AWS SDK Lambda tests - non-Invoke operations + self.validate_aws_sdk_service_normalization("Lambda", "AWS::Lambda") + + # Lambda Invoke with function name + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + ], + ["aws-api", "Lambda", "Invoke", "testFunction"], + ) + self.span_mock.kind = SpanKind.CLIENT + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertEqual(actual_attributes.get(AWS_REMOTE_SERVICE), "testFunction") + + # Lambda Invoke without AWS_LAMBDA_NAME - should fall back to UnknownRemoteService + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + ], + ["aws-api", "Lambda", "Invoke", None], + ) + actual_attributes = _GENERATOR.generate_metric_attributes_dict_from_span(self.span_mock, self.resource).get( + DEPENDENCY_METRIC + ) + self.assertEqual(actual_attributes.get(AWS_REMOTE_SERVICE), "UnknownRemoteService") + def validate_aws_sdk_service_normalization(self, service_name: str, expected_remote_service: str): self._mock_attribute([SpanAttributes.RPC_SYSTEM, SpanAttributes.RPC_SERVICE], ["aws-api", service_name]) self.span_mock.kind = SpanKind.CLIENT From a1ab00e13fb9cbae188c8717854422cea3f32326 Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 16:41:01 +0000 Subject: [PATCH 05/13] add lambda assertions in span client test --- .../test_aws_metric_attribute_generator.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index 6cd842122..6ecf96d3d 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -1286,6 +1286,62 @@ def test_sdk_client_span_with_remote_resource_attributes(self): [AWS_LAMBDA_FUNCTION_NAME, AWS_LAMBDA_FUNCTION_ARN, SpanAttributes.RPC_METHOD], [None, None, None] ) + # Validate behaviour of AWS_LAMBDA_NAME for non-Invoke operations (treated as resource) + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + AWS_LAMBDA_FUNCTION_ARN, + ], + [ + "aws-api", + "Lambda", + "GetFunction", + "testLambdaName", + "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName", + ], + keys, + values, + ) + self._validate_remote_resource_attributes( + "AWS::Lambda::Function", "testLambdaName", "arn:aws:lambda:us-east-1:123456789012:function:testLambdaName" + ) + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + AWS_LAMBDA_FUNCTION_ARN, + ], + [None, None, None, None, None], + ) + + # Validate that Lambda Invoke with function name treats Lambda as a service, not a resource + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + ], + ["aws-api", "Lambda", "Invoke", "testLambdaName"], + keys, + values, + ) + self._validate_remote_resource_attributes(None, None) + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + ], + [None, None, None, None], + ) + self._mock_attribute([SpanAttributes.RPC_SYSTEM], [None]) def test_client_db_span_with_remote_resource_attributes(self): From db1aa06474a7d438e0b7bc8e5b1347355b59b9ec Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 16:56:12 +0000 Subject: [PATCH 06/13] add cfn primary id fallback test case --- .../test_aws_metric_attribute_generator.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index 6ecf96d3d..d122519cf 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -1765,3 +1765,90 @@ def test_set_remote_environment(self): DEPENDENCY_METRIC ) self.assertIsNone(actual_attributes.get(AWS_REMOTE_ENVIRONMENT)) + + def test_cloudformation_primary_identifier_fallback_to_remote_resource_identifier(self): + """Test that when cloudformationPrimaryIdentifier is not explicitly set, + it falls back to use the same value as remoteResourceIdentifier.""" + keys = [] + values = [] + + keys, values = self._mock_attribute([SpanAttributes.RPC_SYSTEM], ["aws-api"], keys, values) + self.span_mock.kind = SpanKind.CLIENT + + # Test case 1: S3 Bucket (no ARN available, should use bucket name for both) + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.AWS_S3_BUCKET], ["S3", "my-test-bucket"], keys, values + ) + self._validate_remote_resource_attributes("AWS::S3::Bucket", "my-test-bucket") + + # Test S3 bucket with special characters + keys, values = self._mock_attribute([SpanAttributes.AWS_S3_BUCKET], ["my-test|bucket^name"], keys, values) + self._validate_remote_resource_attributes("AWS::S3::Bucket", "my-test^|bucket^^name") + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.AWS_S3_BUCKET], [None, None], keys, values + ) + + # Test case 2: SQS Queue by name (no ARN, should use queue name for both) + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, AWS_SQS_QUEUE_NAME], ["SQS", "my-test-queue"], keys, values + ) + self._validate_remote_resource_attributes("AWS::SQS::Queue", "my-test-queue") + + # Test SQS queue with special characters + keys, values = self._mock_attribute([AWS_SQS_QUEUE_NAME], ["my^queue|name"], keys, values) + self._validate_remote_resource_attributes("AWS::SQS::Queue", "my^^queue^|name") + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, AWS_SQS_QUEUE_NAME], [None, None], keys, values + ) + + # Test case 3: DynamoDB Table (no ARN, should use table name for both) + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.AWS_DYNAMODB_TABLE_NAMES], + ["DynamoDB", ["my-test-table"]], + keys, + values, + ) + self._validate_remote_resource_attributes("AWS::DynamoDB::Table", "my-test-table") + + # Test DynamoDB table with special characters + keys, values = self._mock_attribute( + [SpanAttributes.AWS_DYNAMODB_TABLE_NAMES], [["my|test^table"]], keys, values + ) + self._validate_remote_resource_attributes("AWS::DynamoDB::Table", "my^|test^^table") + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.AWS_DYNAMODB_TABLE_NAMES], [None, None], keys, values + ) + + # Test case 4: Kinesis Stream + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, AWS_KINESIS_STREAM_NAME], ["Kinesis", "my-test-stream"], keys, values + ) + self._validate_remote_resource_attributes("AWS::Kinesis::Stream", "my-test-stream") + + # Test Kinesis stream with special characters + keys, values = self._mock_attribute([AWS_KINESIS_STREAM_NAME], ["my-stream^with|chars"], keys, values) + self._validate_remote_resource_attributes("AWS::Kinesis::Stream", "my-stream^^with^|chars") + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, AWS_KINESIS_STREAM_NAME], [None, None], keys, values + ) + + # Test case 5: Lambda Function (non-invoke operation, no ARN) + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.RPC_METHOD, AWS_LAMBDA_FUNCTION_NAME], + ["Lambda", "GetFunction", "my-test-function"], + keys, + values, + ) + self._validate_remote_resource_attributes("AWS::Lambda::Function", "my-test-function") + + # Test Lambda function with special characters + keys, values = self._mock_attribute([AWS_LAMBDA_FUNCTION_NAME], ["my-function|with^chars"], keys, values) + self._validate_remote_resource_attributes("AWS::Lambda::Function", "my-function^|with^^chars") + keys, values = self._mock_attribute( + [SpanAttributes.RPC_SERVICE, SpanAttributes.RPC_METHOD, AWS_LAMBDA_FUNCTION_NAME], + [None, None, None], + keys, + values, + ) + + keys, values = self._mock_attribute([SpanAttributes.RPC_SYSTEM], [None], keys, values) From 08ba010db931e48c81788a0a94543ba6ed82f64a Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 17:07:26 +0000 Subject: [PATCH 07/13] add missing special char assertions for sdk client span test case --- .../test_aws_metric_attribute_generator.py | 96 +++++++++++++++++-- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index d122519cf..3dc7478f6 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -1179,6 +1179,20 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None]) + # Validate behaviour of AWS_SECRETSMANAGER_SECRET_ARN attribute with special chars, then remove it. + self._mock_attribute( + [AWS_SECRETSMANAGER_SECRET_ARN], + ["arn:aws:secretsmanager:us-east-1:123456789012:secret:secret|name^test-lERW9H"], + keys, + values, + ) + self._validate_remote_resource_attributes( + "AWS::SecretsManager::Secret", + "secret^|name^^test-lERW9H", + "arn:aws:secretsmanager:us-east-1:123456789012:secret:secret^|name^^test-lERW9H", + ) + self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None]) + # Validate behaviour of AWS_SNS_TOPIC_ARN attribute, then remove it. self._mock_attribute([AWS_SNS_TOPIC_ARN], ["arn:aws:sns:us-west-2:012345678901:test_topic"], keys, values) self._validate_remote_resource_attributes( @@ -1186,6 +1200,13 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_SNS_TOPIC_ARN], [None]) + # Validate behaviour of AWS_SNS_TOPIC_ARN attribute with special chars, then remove it. + self._mock_attribute([AWS_SNS_TOPIC_ARN], ["arn:aws:sns:us-west-2:012345678901:test|topic^name"], keys, values) + self._validate_remote_resource_attributes( + "AWS::SNS::Topic", "test^|topic^^name", "arn:aws:sns:us-west-2:012345678901:test^|topic^^name" + ) + self._mock_attribute([AWS_SNS_TOPIC_ARN], [None]) + # Validate behaviour of AWS_STEPFUNCTIONS_STATEMACHINE_ARN attribute, then remove it. self._mock_attribute( [AWS_STEPFUNCTIONS_STATEMACHINE_ARN], @@ -1200,6 +1221,20 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_STEPFUNCTIONS_STATEMACHINE_ARN], [None]) + # Validate behaviour of AWS_STEPFUNCTIONS_STATEMACHINE_ARN attribute with special chars, then remove it. + self._mock_attribute( + [AWS_STEPFUNCTIONS_STATEMACHINE_ARN], + ["arn:aws:states:us-east-1:123456789012:stateMachine:test|state^machine"], + keys, + values, + ) + self._validate_remote_resource_attributes( + "AWS::StepFunctions::StateMachine", + "test^|state^^machine", + "arn:aws:states:us-east-1:123456789012:stateMachine:test^|state^^machine", + ) + self._mock_attribute([AWS_STEPFUNCTIONS_STATEMACHINE_ARN], [None]) + # Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN attribute, then remove it. self._mock_attribute( [AWS_STEPFUNCTIONS_ACTIVITY_ARN], @@ -1214,22 +1249,30 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_STEPFUNCTIONS_ACTIVITY_ARN], [None]) - # Validate behaviour of AWS_LAMBDA_RESOURCEMAPPING_ID attribute, then remove it. - self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], ["aws_event_source_mapping_id"], keys, values) - self._validate_remote_resource_attributes("AWS::Lambda::EventSourceMapping", "aws_event_source_mapping_id") - self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], [None]) - - # Validate behaviour of AWS_LAMBDA_RESOURCE_MAPPING_ID, - # then remove it. + # Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN attribute with special chars, then remove it. self._mock_attribute( - [AWS_LAMBDA_RESOURCEMAPPING_ID], - ["aws_event_source_mapping_id"], + [AWS_STEPFUNCTIONS_ACTIVITY_ARN], + ["arn:aws:states:us-east-1:007003123456789012:activity:test|Activity^name"], keys, values, ) + self._validate_remote_resource_attributes( + "AWS::StepFunctions::Activity", + "test^|Activity^^name", + "arn:aws:states:us-east-1:007003123456789012:activity:test^|Activity^^name", + ) + self._mock_attribute([AWS_STEPFUNCTIONS_ACTIVITY_ARN], [None]) + + # Validate behaviour of AWS_LAMBDA_RESOURCEMAPPING_ID attribute, then remove it. + self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], ["aws_event_source_mapping_id"], keys, values) self._validate_remote_resource_attributes("AWS::Lambda::EventSourceMapping", "aws_event_source_mapping_id") self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], [None]) + # Validate behaviour of AWS_LAMBDA_RESOURCEMAPPING_ID attribute with special chars, then remove it. + self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], ["aws_event|source^mapping_id"], keys, values) + self._validate_remote_resource_attributes("AWS::Lambda::EventSourceMapping", "aws_event^|source^^mapping_id") + self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], [None]) + # Test AWS Lambda Invoke scenario with default lambda remote environment self.span_mock.kind = SpanKind.CLIENT self._mock_attribute( @@ -1319,6 +1362,41 @@ def test_sdk_client_span_with_remote_resource_attributes(self): [None, None, None, None, None], ) + # Validate behaviour of AWS_LAMBDA_NAME for non-Invoke operations with special chars + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + AWS_LAMBDA_FUNCTION_ARN, + ], + [ + "aws-api", + "Lambda", + "GetFunction", + "test|Lambda^Name", + "arn:aws:lambda:us-east-1:123456789012:function:test|Lambda^Name", + ], + keys, + values, + ) + self._validate_remote_resource_attributes( + "AWS::Lambda::Function", + "test^|Lambda^^Name", + "arn:aws:lambda:us-east-1:123456789012:function:test^|Lambda^^Name", + ) + self._mock_attribute( + [ + SpanAttributes.RPC_SYSTEM, + SpanAttributes.RPC_SERVICE, + SpanAttributes.RPC_METHOD, + AWS_LAMBDA_FUNCTION_NAME, + AWS_LAMBDA_FUNCTION_ARN, + ], + [None, None, None, None, None], + ) + # Validate that Lambda Invoke with function name treats Lambda as a service, not a resource self._mock_attribute( [ From d3b7e6b3b09b6bc363e7f485fcb39a0c105c6ccc Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 20:22:14 +0000 Subject: [PATCH 08/13] add conditional check before extracting sqs queue url for cfn primary id in queue name case --- .../opentelemetry/distro/_aws_metric_attribute_generator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py index 77a85ebc1..9bcae9dab 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py @@ -413,7 +413,9 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri elif is_key_present(span, AWS_SQS_QUEUE_NAME): remote_resource_type = _NORMALIZED_SQS_SERVICE_NAME + "::Queue" remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_NAME)) - cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_URL)) + # If queue URL is also present, use it as the CloudFormation primary identifier + if is_key_present(span, AWS_SQS_QUEUE_URL): + cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_URL)) elif is_key_present(span, AWS_SQS_QUEUE_URL): remote_resource_type = _NORMALIZED_SQS_SERVICE_NAME + "::Queue" remote_resource_identifier = _escape_delimiters( From 95ab51458abcbc326770cc57dfedda194fe47cde Mon Sep 17 00:00:00 2001 From: Michael He Date: Sun, 8 Jun 2025 21:50:38 +0000 Subject: [PATCH 09/13] add docstring for set remote environment method --- .../opentelemetry/distro/_aws_metric_attribute_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py index 9bcae9dab..e76e3df27 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py @@ -499,6 +499,10 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri def _set_remote_environment(span: ReadableSpan, attributes: BoundedAttributes) -> None: + """ + Remote environment is used to identify the environment of downstream services. Currently only + set to "lambda:default" for Lambda Invoke operations when aws-api system is detected. + """ # We want to treat downstream Lambdas as a service rather than a resource because # Application Signals topology map gets disconnected due to conflicting Lambda Entity # definitions From 32c1059e132dfc4a2c61eae68a4fd64f74fa36a1 Mon Sep 17 00:00:00 2001 From: Michael He Date: Mon, 9 Jun 2025 23:00:05 +0000 Subject: [PATCH 10/13] Revert "add missing special char assertions for sdk client span test case" to scope down PR This reverts commit 08ba010db931e48c81788a0a94543ba6ed82f64a. --- .../test_aws_metric_attribute_generator.py | 96 ++----------------- 1 file changed, 9 insertions(+), 87 deletions(-) diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py index 3dc7478f6..d122519cf 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py @@ -1179,20 +1179,6 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None]) - # Validate behaviour of AWS_SECRETSMANAGER_SECRET_ARN attribute with special chars, then remove it. - self._mock_attribute( - [AWS_SECRETSMANAGER_SECRET_ARN], - ["arn:aws:secretsmanager:us-east-1:123456789012:secret:secret|name^test-lERW9H"], - keys, - values, - ) - self._validate_remote_resource_attributes( - "AWS::SecretsManager::Secret", - "secret^|name^^test-lERW9H", - "arn:aws:secretsmanager:us-east-1:123456789012:secret:secret^|name^^test-lERW9H", - ) - self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None]) - # Validate behaviour of AWS_SNS_TOPIC_ARN attribute, then remove it. self._mock_attribute([AWS_SNS_TOPIC_ARN], ["arn:aws:sns:us-west-2:012345678901:test_topic"], keys, values) self._validate_remote_resource_attributes( @@ -1200,13 +1186,6 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_SNS_TOPIC_ARN], [None]) - # Validate behaviour of AWS_SNS_TOPIC_ARN attribute with special chars, then remove it. - self._mock_attribute([AWS_SNS_TOPIC_ARN], ["arn:aws:sns:us-west-2:012345678901:test|topic^name"], keys, values) - self._validate_remote_resource_attributes( - "AWS::SNS::Topic", "test^|topic^^name", "arn:aws:sns:us-west-2:012345678901:test^|topic^^name" - ) - self._mock_attribute([AWS_SNS_TOPIC_ARN], [None]) - # Validate behaviour of AWS_STEPFUNCTIONS_STATEMACHINE_ARN attribute, then remove it. self._mock_attribute( [AWS_STEPFUNCTIONS_STATEMACHINE_ARN], @@ -1221,20 +1200,6 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_STEPFUNCTIONS_STATEMACHINE_ARN], [None]) - # Validate behaviour of AWS_STEPFUNCTIONS_STATEMACHINE_ARN attribute with special chars, then remove it. - self._mock_attribute( - [AWS_STEPFUNCTIONS_STATEMACHINE_ARN], - ["arn:aws:states:us-east-1:123456789012:stateMachine:test|state^machine"], - keys, - values, - ) - self._validate_remote_resource_attributes( - "AWS::StepFunctions::StateMachine", - "test^|state^^machine", - "arn:aws:states:us-east-1:123456789012:stateMachine:test^|state^^machine", - ) - self._mock_attribute([AWS_STEPFUNCTIONS_STATEMACHINE_ARN], [None]) - # Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN attribute, then remove it. self._mock_attribute( [AWS_STEPFUNCTIONS_ACTIVITY_ARN], @@ -1249,28 +1214,20 @@ def test_sdk_client_span_with_remote_resource_attributes(self): ) self._mock_attribute([AWS_STEPFUNCTIONS_ACTIVITY_ARN], [None]) - # Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN attribute with special chars, then remove it. - self._mock_attribute( - [AWS_STEPFUNCTIONS_ACTIVITY_ARN], - ["arn:aws:states:us-east-1:007003123456789012:activity:test|Activity^name"], - keys, - values, - ) - self._validate_remote_resource_attributes( - "AWS::StepFunctions::Activity", - "test^|Activity^^name", - "arn:aws:states:us-east-1:007003123456789012:activity:test^|Activity^^name", - ) - self._mock_attribute([AWS_STEPFUNCTIONS_ACTIVITY_ARN], [None]) - # Validate behaviour of AWS_LAMBDA_RESOURCEMAPPING_ID attribute, then remove it. self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], ["aws_event_source_mapping_id"], keys, values) self._validate_remote_resource_attributes("AWS::Lambda::EventSourceMapping", "aws_event_source_mapping_id") self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], [None]) - # Validate behaviour of AWS_LAMBDA_RESOURCEMAPPING_ID attribute with special chars, then remove it. - self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], ["aws_event|source^mapping_id"], keys, values) - self._validate_remote_resource_attributes("AWS::Lambda::EventSourceMapping", "aws_event^|source^^mapping_id") + # Validate behaviour of AWS_LAMBDA_RESOURCE_MAPPING_ID, + # then remove it. + self._mock_attribute( + [AWS_LAMBDA_RESOURCEMAPPING_ID], + ["aws_event_source_mapping_id"], + keys, + values, + ) + self._validate_remote_resource_attributes("AWS::Lambda::EventSourceMapping", "aws_event_source_mapping_id") self._mock_attribute([AWS_LAMBDA_RESOURCEMAPPING_ID], [None]) # Test AWS Lambda Invoke scenario with default lambda remote environment @@ -1362,41 +1319,6 @@ def test_sdk_client_span_with_remote_resource_attributes(self): [None, None, None, None, None], ) - # Validate behaviour of AWS_LAMBDA_NAME for non-Invoke operations with special chars - self._mock_attribute( - [ - SpanAttributes.RPC_SYSTEM, - SpanAttributes.RPC_SERVICE, - SpanAttributes.RPC_METHOD, - AWS_LAMBDA_FUNCTION_NAME, - AWS_LAMBDA_FUNCTION_ARN, - ], - [ - "aws-api", - "Lambda", - "GetFunction", - "test|Lambda^Name", - "arn:aws:lambda:us-east-1:123456789012:function:test|Lambda^Name", - ], - keys, - values, - ) - self._validate_remote_resource_attributes( - "AWS::Lambda::Function", - "test^|Lambda^^Name", - "arn:aws:lambda:us-east-1:123456789012:function:test^|Lambda^^Name", - ) - self._mock_attribute( - [ - SpanAttributes.RPC_SYSTEM, - SpanAttributes.RPC_SERVICE, - SpanAttributes.RPC_METHOD, - AWS_LAMBDA_FUNCTION_NAME, - AWS_LAMBDA_FUNCTION_ARN, - ], - [None, None, None, None, None], - ) - # Validate that Lambda Invoke with function name treats Lambda as a service, not a resource self._mock_attribute( [ From efe9086f04abb73575a3b1e9e54d5203071b72af Mon Sep 17 00:00:00 2001 From: Michael He Date: Mon, 9 Jun 2025 23:07:56 +0000 Subject: [PATCH 11/13] Revert "add conditional check before extracting sqs queue url for cfn primary id in queue name case" to scope down PR This reverts commit d3b7e6b3b09b6bc363e7f485fcb39a0c105c6ccc. --- .../opentelemetry/distro/_aws_metric_attribute_generator.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py index e76e3df27..db1ac4264 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py @@ -413,9 +413,7 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri elif is_key_present(span, AWS_SQS_QUEUE_NAME): remote_resource_type = _NORMALIZED_SQS_SERVICE_NAME + "::Queue" remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_NAME)) - # If queue URL is also present, use it as the CloudFormation primary identifier - if is_key_present(span, AWS_SQS_QUEUE_URL): - cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_URL)) + cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_URL)) elif is_key_present(span, AWS_SQS_QUEUE_URL): remote_resource_type = _NORMALIZED_SQS_SERVICE_NAME + "::Queue" remote_resource_identifier = _escape_delimiters( From f72191aa930a7ce7b1250d9f73ae7f916a34eec9 Mon Sep 17 00:00:00 2001 From: Michael He Date: Tue, 10 Jun 2025 16:53:48 +0000 Subject: [PATCH 12/13] add comment for lambda constants --- .../opentelemetry/distro/_aws_metric_attribute_generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py index db1ac4264..3165fef39 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py @@ -103,6 +103,7 @@ _NORMALIZED_LAMBDA_SERVICE_NAME: str = "AWS::Lambda" _DB_CONNECTION_STRING_TYPE: str = "DB::Connection" +# Constants for Lambda operations _LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT: str = "LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT" _LAMBDA_INVOKE_OPERATION: str = "Invoke" From 79cfa082301e1f28aab25df29122a48ce081c0c3 Mon Sep 17 00:00:00 2001 From: Michael He Date: Tue, 10 Jun 2025 16:54:58 +0000 Subject: [PATCH 13/13] remove unecessary comments about aws lambda function name attribute --- .../opentelemetry/distro/_aws_metric_attribute_generator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py index 3165fef39..ec5b693ed 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py @@ -327,8 +327,6 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str # Special handling for Lambda invoke operations if _is_lambda_invoke_operation(span): - # AWS_LAMBDA_FUNCTION_NAME is guaranteed to contain function name (not ARN) - # due to logic in Lambda botocore patches during instrumentation lambda_function_name = span.attributes.get(AWS_LAMBDA_FUNCTION_NAME) # If Lambda name is not present, use UnknownRemoteService # This is intentional - we want to clearly indicate when the Lambda function name @@ -471,8 +469,6 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri # see normalize_remote_service_name for more information. if not _is_lambda_invoke_operation(span): remote_resource_type = _NORMALIZED_LAMBDA_SERVICE_NAME + "::Function" - # AWS_LAMBDA_FUNCTION_NAME is guaranteed to contain function name (not ARN) - # due to logic in Lambda botocore patches during instrumentation remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_NAME)) cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_ARN)) elif is_key_present(span, AWS_LAMBDA_RESOURCEMAPPING_ID):