From 18d196039de9571c6d95dcac72260820f9f1de69 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 29 Dec 2025 17:12:37 +0100 Subject: [PATCH 1/2] botocore: move away from SpanAttributes --- .../instrumentation/botocore/__init__.py | 17 ++- .../botocore/extensions/dynamodb.py | 124 ++++++++++-------- .../botocore/extensions/lmbd.py | 14 +- .../tests/test_botocore_dynamodb.py | 60 +++++---- .../tests/test_botocore_instrumentation.py | 48 ++++--- .../tests/test_botocore_lambda.py | 29 ++-- 6 files changed, 162 insertions(+), 130 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py index a5c4bd1c19..6fd42d6ab4 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py @@ -128,7 +128,14 @@ def response_hook(span, service_name, operation_name, result): from opentelemetry.semconv._incubating.attributes.cloud_attributes import ( CLOUD_REGION, ) -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv._incubating.attributes.http_attributes import ( + HTTP_STATUS_CODE, +) +from opentelemetry.semconv._incubating.attributes.rpc_attributes import ( + RPC_METHOD, + RPC_SERVICE, + RPC_SYSTEM, +) from opentelemetry.trace import get_tracer from opentelemetry.trace.span import Span @@ -292,9 +299,9 @@ def _patched_api_call(self, original_func, instance, args, kwargs): return original_func(*args, **kwargs) attributes = { - SpanAttributes.RPC_SYSTEM: "aws-api", - SpanAttributes.RPC_SERVICE: call_context.service_id, - SpanAttributes.RPC_METHOD: call_context.operation, + RPC_SYSTEM: "aws-api", + RPC_SERVICE: call_context.service_id, + RPC_METHOD: call_context.operation, CLOUD_REGION: call_context.region, **get_server_attributes(call_context.endpoint_url), } @@ -390,7 +397,7 @@ def _apply_response_attributes(span: Span, result): status_code = metadata.get("HTTPStatusCode") if status_code is not None: - span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code) + span.set_attribute(HTTP_STATUS_CODE, status_code) def _determine_call_context( diff --git a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/dynamodb.py b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/dynamodb.py index 850f32ab69..5bd8a5a7b0 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/dynamodb.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/dynamodb.py @@ -25,7 +25,13 @@ _BotocoreInstrumentorContext, _BotoResultT, ) -from opentelemetry.semconv.trace import DbSystemValues, SpanAttributes +from opentelemetry.semconv._incubating.attributes import ( + aws_attributes, + db_attributes, +) +from opentelemetry.semconv._incubating.attributes.net_attributes import ( + NET_PEER_NAME, +) from opentelemetry.trace.span import Span from opentelemetry.util.types import AttributeValue @@ -126,10 +132,10 @@ def add_response_attributes( class _OpBatchGetItem(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_REQITEMS_TABLE_NAMES, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_REQITEMS_TABLE_NAMES, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP, } @classmethod @@ -139,11 +145,11 @@ def operation_name(cls): class _OpBatchWriteItem(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_REQITEMS_TABLE_NAMES, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_REQITEMS_TABLE_NAMES, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP, - SpanAttributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP, + aws_attributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, } @classmethod @@ -153,13 +159,13 @@ def operation_name(cls): class _OpCreateTable(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } request_attributes = { - SpanAttributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES: _REQ_GLOBAL_SEC_INDEXES, - SpanAttributes.AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES: _REQ_LOCAL_SEC_INDEXES, - SpanAttributes.AWS_DYNAMODB_PROVISIONED_READ_CAPACITY: _REQ_PROV_READ_CAP, - SpanAttributes.AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY: _REQ_PROV_WRITE_CAP, + aws_attributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES: _REQ_GLOBAL_SEC_INDEXES, + aws_attributes.AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES: _REQ_LOCAL_SEC_INDEXES, + aws_attributes.AWS_DYNAMODB_PROVISIONED_READ_CAPACITY: _REQ_PROV_READ_CAP, + aws_attributes.AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY: _REQ_PROV_WRITE_CAP, } @classmethod @@ -169,11 +175,11 @@ def operation_name(cls): class _OpDeleteItem(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, - SpanAttributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, + aws_attributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, } @classmethod @@ -183,7 +189,7 @@ def operation_name(cls): class _OpDeleteTable(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } @classmethod @@ -193,7 +199,7 @@ def operation_name(cls): class _OpDescribeTable(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } @classmethod @@ -203,14 +209,14 @@ def operation_name(cls): class _OpGetItem(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } request_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSISTENT_READ: _REQ_CONSISTENT_READ, - SpanAttributes.AWS_DYNAMODB_PROJECTION: _REQ_PROJECTION, + aws_attributes.AWS_DYNAMODB_CONSISTENT_READ: _REQ_CONSISTENT_READ, + aws_attributes.AWS_DYNAMODB_PROJECTION: _REQ_PROJECTION, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, } @classmethod @@ -220,14 +226,14 @@ def operation_name(cls): class _OpListTables(_DynamoDbOperation): request_attributes = { - SpanAttributes.AWS_DYNAMODB_EXCLUSIVE_START_TABLE: ( + aws_attributes.AWS_DYNAMODB_EXCLUSIVE_START_TABLE: ( "ExclusiveStartTableName", None, ), - SpanAttributes.AWS_DYNAMODB_LIMIT: _REQ_LIMIT, + aws_attributes.AWS_DYNAMODB_LIMIT: _REQ_LIMIT, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_COUNT: ( + aws_attributes.AWS_DYNAMODB_TABLE_COUNT: ( "TableNames", _conv_val_to_len, ), @@ -240,11 +246,11 @@ def operation_name(cls): class _OpPutItem(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, - SpanAttributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, + aws_attributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, } @classmethod @@ -254,19 +260,19 @@ def operation_name(cls): class _OpQuery(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } request_attributes = { - SpanAttributes.AWS_DYNAMODB_SCAN_FORWARD: ("ScanIndexForward", None), - SpanAttributes.AWS_DYNAMODB_ATTRIBUTES_TO_GET: _REQ_ATTRS_TO_GET, - SpanAttributes.AWS_DYNAMODB_CONSISTENT_READ: _REQ_CONSISTENT_READ, - SpanAttributes.AWS_DYNAMODB_INDEX_NAME: _REQ_INDEX_NAME, - SpanAttributes.AWS_DYNAMODB_LIMIT: _REQ_LIMIT, - SpanAttributes.AWS_DYNAMODB_PROJECTION: _REQ_PROJECTION, - SpanAttributes.AWS_DYNAMODB_SELECT: _REQ_SELECT, + aws_attributes.AWS_DYNAMODB_SCAN_FORWARD: ("ScanIndexForward", None), + aws_attributes.AWS_DYNAMODB_ATTRIBUTES_TO_GET: _REQ_ATTRS_TO_GET, + aws_attributes.AWS_DYNAMODB_CONSISTENT_READ: _REQ_CONSISTENT_READ, + aws_attributes.AWS_DYNAMODB_INDEX_NAME: _REQ_INDEX_NAME, + aws_attributes.AWS_DYNAMODB_LIMIT: _REQ_LIMIT, + aws_attributes.AWS_DYNAMODB_PROJECTION: _REQ_PROJECTION, + aws_attributes.AWS_DYNAMODB_SELECT: _REQ_SELECT, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, } @classmethod @@ -276,22 +282,22 @@ def operation_name(cls): class _OpScan(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } request_attributes = { - SpanAttributes.AWS_DYNAMODB_SEGMENT: ("Segment", None), - SpanAttributes.AWS_DYNAMODB_TOTAL_SEGMENTS: ("TotalSegments", None), - SpanAttributes.AWS_DYNAMODB_ATTRIBUTES_TO_GET: _REQ_ATTRS_TO_GET, - SpanAttributes.AWS_DYNAMODB_CONSISTENT_READ: _REQ_CONSISTENT_READ, - SpanAttributes.AWS_DYNAMODB_INDEX_NAME: _REQ_INDEX_NAME, - SpanAttributes.AWS_DYNAMODB_LIMIT: _REQ_LIMIT, - SpanAttributes.AWS_DYNAMODB_PROJECTION: _REQ_PROJECTION, - SpanAttributes.AWS_DYNAMODB_SELECT: _REQ_SELECT, + aws_attributes.AWS_DYNAMODB_SEGMENT: ("Segment", None), + aws_attributes.AWS_DYNAMODB_TOTAL_SEGMENTS: ("TotalSegments", None), + aws_attributes.AWS_DYNAMODB_ATTRIBUTES_TO_GET: _REQ_ATTRS_TO_GET, + aws_attributes.AWS_DYNAMODB_CONSISTENT_READ: _REQ_CONSISTENT_READ, + aws_attributes.AWS_DYNAMODB_INDEX_NAME: _REQ_INDEX_NAME, + aws_attributes.AWS_DYNAMODB_LIMIT: _REQ_LIMIT, + aws_attributes.AWS_DYNAMODB_PROJECTION: _REQ_PROJECTION, + aws_attributes.AWS_DYNAMODB_SELECT: _REQ_SELECT, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_COUNT: ("Count", None), - SpanAttributes.AWS_DYNAMODB_SCANNED_COUNT: ("ScannedCount", None), - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, + aws_attributes.AWS_DYNAMODB_COUNT: ("Count", None), + aws_attributes.AWS_DYNAMODB_SCANNED_COUNT: ("ScannedCount", None), + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, } @classmethod @@ -301,11 +307,11 @@ def operation_name(cls): class _OpUpdateItem(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } response_attributes = { - SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, - SpanAttributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, + aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY: _RES_CONSUMED_CAP_SINGLE, + aws_attributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS: _RES_ITEM_COL_METRICS, } @classmethod @@ -315,19 +321,19 @@ def operation_name(cls): class _OpUpdateTable(_DynamoDbOperation): start_attributes = { - SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, + aws_attributes.AWS_DYNAMODB_TABLE_NAMES: _REQ_TABLE_NAME, } request_attributes = { - SpanAttributes.AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS: ( + aws_attributes.AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS: ( "AttributeDefinitions", _conv_list_to_json_list, ), - SpanAttributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES: ( + aws_attributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES: ( "GlobalSecondaryIndexUpdates", _conv_list_to_json_list, ), - SpanAttributes.AWS_DYNAMODB_PROVISIONED_READ_CAPACITY: _REQ_PROV_READ_CAP, - SpanAttributes.AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY: _REQ_PROV_WRITE_CAP, + aws_attributes.AWS_DYNAMODB_PROVISIONED_READ_CAPACITY: _REQ_PROV_READ_CAP, + aws_attributes.AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY: _REQ_PROV_WRITE_CAP, } @classmethod @@ -354,9 +360,11 @@ def __init__(self, call_context: _AwsSdkCallContext): self._op = _OPERATION_MAPPING.get(call_context.operation) def extract_attributes(self, attributes: _AttributeMapT): - attributes[SpanAttributes.DB_SYSTEM] = DbSystemValues.DYNAMODB.value - attributes[SpanAttributes.DB_OPERATION] = self._call_context.operation - attributes[SpanAttributes.NET_PEER_NAME] = self._get_peer_name() + attributes[db_attributes.DB_SYSTEM] = ( + db_attributes.DbSystemValues.DYNAMODB.value + ) + attributes[db_attributes.DB_OPERATION] = self._call_context.operation + attributes[NET_PEER_NAME] = self._get_peer_name() if self._op is None: return diff --git a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/lmbd.py b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/lmbd.py index 0d4a1656a3..15a0708eb8 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/lmbd.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/lmbd.py @@ -25,7 +25,11 @@ _BotocoreInstrumentorContext, ) from opentelemetry.propagate import inject -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv._incubating.attributes.faas_attributes import ( + FAAS_INVOKED_NAME, + FAAS_INVOKED_PROVIDER, + FAAS_INVOKED_REGION, +) from opentelemetry.trace.span import Span @@ -62,11 +66,9 @@ def operation_name(cls): def extract_attributes( cls, call_context: _AwsSdkCallContext, attributes: _AttributeMapT ): - attributes[SpanAttributes.FAAS_INVOKED_PROVIDER] = "aws" - attributes[SpanAttributes.FAAS_INVOKED_NAME] = ( - cls._parse_function_name(call_context) - ) - attributes[SpanAttributes.FAAS_INVOKED_REGION] = call_context.region + attributes[FAAS_INVOKED_PROVIDER] = "aws" + attributes[FAAS_INVOKED_NAME] = cls._parse_function_name(call_context) + attributes[FAAS_INVOKED_REGION] = call_context.region @classmethod def _parse_function_name(cls, call_context: _AwsSdkCallContext): diff --git a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_dynamodb.py b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_dynamodb.py index e3c658d279..354b11c37e 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_dynamodb.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_dynamodb.py @@ -23,7 +23,13 @@ _BotocoreInstrumentorContext, _DynamoDbExtension, ) -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv._incubating.attributes import ( + aws_attributes, + db_attributes, +) +from opentelemetry.semconv._incubating.attributes.net_attributes import ( + NET_PEER_NAME, +) from opentelemetry.test.test_base import TestBase from opentelemetry.trace.span import Span @@ -101,24 +107,24 @@ def assert_span(self, operation: str) -> Span: self.assertEqual(1, len(spans)) span = spans[0] - self.assertEqual("dynamodb", span.attributes[SpanAttributes.DB_SYSTEM]) + self.assertEqual("dynamodb", span.attributes[db_attributes.DB_SYSTEM]) self.assertEqual( - operation, span.attributes[SpanAttributes.DB_OPERATION] + operation, span.attributes[db_attributes.DB_OPERATION] ) self.assertEqual( "dynamodb.us-west-2.amazonaws.com", - span.attributes[SpanAttributes.NET_PEER_NAME], + span.attributes[NET_PEER_NAME], ) return span def assert_table_names(self, span: Span, *table_names): self.assertEqual( tuple(table_names), - span.attributes[SpanAttributes.AWS_DYNAMODB_TABLE_NAMES], + span.attributes[aws_attributes.AWS_DYNAMODB_TABLE_NAMES], ) def assert_consumed_capacity(self, span: Span, *table_names): - cap = span.attributes[SpanAttributes.AWS_DYNAMODB_CONSUMED_CAPACITY] + cap = span.attributes[aws_attributes.AWS_DYNAMODB_CONSUMED_CAPACITY] self.assertEqual(len(cap), len(table_names)) cap_tables = set() for item in cap: @@ -130,50 +136,50 @@ def assert_consumed_capacity(self, span: Span, *table_names): def assert_item_col_metrics(self, span: Span): actual = span.attributes[ - SpanAttributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS + aws_attributes.AWS_DYNAMODB_ITEM_COLLECTION_METRICS ] self.assertIsNotNone(actual) json.loads(actual) def assert_provisioned_read_cap(self, span: Span, expected: int): actual = span.attributes[ - SpanAttributes.AWS_DYNAMODB_PROVISIONED_READ_CAPACITY + aws_attributes.AWS_DYNAMODB_PROVISIONED_READ_CAPACITY ] self.assertEqual(expected, actual) def assert_provisioned_write_cap(self, span: Span, expected: int): actual = span.attributes[ - SpanAttributes.AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY + aws_attributes.AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY ] self.assertEqual(expected, actual) def assert_consistent_read(self, span: Span, expected: bool): - actual = span.attributes[SpanAttributes.AWS_DYNAMODB_CONSISTENT_READ] + actual = span.attributes[aws_attributes.AWS_DYNAMODB_CONSISTENT_READ] self.assertEqual(expected, actual) def assert_projection(self, span: Span, expected: str): - actual = span.attributes[SpanAttributes.AWS_DYNAMODB_PROJECTION] + actual = span.attributes[aws_attributes.AWS_DYNAMODB_PROJECTION] self.assertEqual(expected, actual) def assert_attributes_to_get(self, span: Span, *attrs): self.assertEqual( tuple(attrs), - span.attributes[SpanAttributes.AWS_DYNAMODB_ATTRIBUTES_TO_GET], + span.attributes[aws_attributes.AWS_DYNAMODB_ATTRIBUTES_TO_GET], ) def assert_index_name(self, span: Span, expected: str): self.assertEqual( - expected, span.attributes[SpanAttributes.AWS_DYNAMODB_INDEX_NAME] + expected, span.attributes[aws_attributes.AWS_DYNAMODB_INDEX_NAME] ) def assert_limit(self, span: Span, expected: int): self.assertEqual( - expected, span.attributes[SpanAttributes.AWS_DYNAMODB_LIMIT] + expected, span.attributes[aws_attributes.AWS_DYNAMODB_LIMIT] ) def assert_select(self, span: Span, expected: str): self.assertEqual( - expected, span.attributes[SpanAttributes.AWS_DYNAMODB_SELECT] + expected, span.attributes[aws_attributes.AWS_DYNAMODB_SELECT] ) def assert_extension_item_col_metrics(self, operation: str): @@ -260,13 +266,13 @@ def test_create_table(self): self.assertEqual( (json.dumps(global_sec_idx),), span.attributes[ - SpanAttributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES + aws_attributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES ], ) self.assertEqual( (json.dumps(local_sec_idx),), span.attributes[ - SpanAttributes.AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES + aws_attributes.AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES ], ) self.assert_provisioned_read_cap(span, 42) @@ -365,12 +371,12 @@ def test_list_tables(self): span = self.assert_span("ListTables") self.assertEqual( "my_table", - span.attributes[SpanAttributes.AWS_DYNAMODB_EXCLUSIVE_START_TABLE], + span.attributes[aws_attributes.AWS_DYNAMODB_EXCLUSIVE_START_TABLE], ) self.assertEqual( - 1, span.attributes[SpanAttributes.AWS_DYNAMODB_TABLE_COUNT] + 1, span.attributes[aws_attributes.AWS_DYNAMODB_TABLE_COUNT] ) - self.assertEqual(5, span.attributes[SpanAttributes.AWS_DYNAMODB_LIMIT]) + self.assertEqual(5, span.attributes[aws_attributes.AWS_DYNAMODB_LIMIT]) @mock_aws def test_put_item(self): @@ -418,7 +424,7 @@ def test_query(self): span = self.assert_span("Query") self.assert_table_names(span, self.default_table_name) self.assertTrue( - span.attributes[SpanAttributes.AWS_DYNAMODB_SCAN_FORWARD] + span.attributes[aws_attributes.AWS_DYNAMODB_SCAN_FORWARD] ) self.assert_attributes_to_get(span, "id") self.assert_consistent_read(span, True) @@ -448,14 +454,14 @@ def test_scan(self): span = self.assert_span("Scan") self.assert_table_names(span, self.default_table_name) self.assertEqual( - 16, span.attributes[SpanAttributes.AWS_DYNAMODB_SEGMENT] + 16, span.attributes[aws_attributes.AWS_DYNAMODB_SEGMENT] ) self.assertEqual( - 17, span.attributes[SpanAttributes.AWS_DYNAMODB_TOTAL_SEGMENTS] + 17, span.attributes[aws_attributes.AWS_DYNAMODB_TOTAL_SEGMENTS] ) - self.assertEqual(0, span.attributes[SpanAttributes.AWS_DYNAMODB_COUNT]) + self.assertEqual(0, span.attributes[aws_attributes.AWS_DYNAMODB_COUNT]) self.assertEqual( - 0, span.attributes[SpanAttributes.AWS_DYNAMODB_SCANNED_COUNT] + 0, span.attributes[aws_attributes.AWS_DYNAMODB_SCANNED_COUNT] ) self.assert_attributes_to_get(span, "id", "idl") self.assert_consistent_read(span, True) @@ -517,11 +523,11 @@ def test_update_table(self): self.assert_provisioned_write_cap(span, 19) self.assertEqual( (json.dumps(attr_definition),), - span.attributes[SpanAttributes.AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS], + span.attributes[aws_attributes.AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS], ) self.assertEqual( (json.dumps(global_sec_idx_updates),), span.attributes[ - SpanAttributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES + aws_attributes.AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES ], ) diff --git a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py index 6ebb074deb..4cc259a860 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py @@ -27,10 +27,22 @@ ) from opentelemetry.propagate import get_global_textmap, set_global_textmap from opentelemetry.propagators.aws.aws_xray_propagator import TRACE_HEADER_KEY +from opentelemetry.semconv._incubating.attributes import rpc_attributes from opentelemetry.semconv._incubating.attributes.cloud_attributes import ( CLOUD_REGION, ) -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv._incubating.attributes.exception_attributes import ( + EXCEPTION_MESSAGE, + EXCEPTION_STACKTRACE, + EXCEPTION_TYPE, +) +from opentelemetry.semconv._incubating.attributes.http_attributes import ( + HTTP_STATUS_CODE, +) +from opentelemetry.semconv._incubating.attributes.server_attributes import ( + SERVER_ADDRESS, + SERVER_PORT, +) from opentelemetry.test.mock_textmap import MockTextMapPropagator from opentelemetry.test.test_base import TestBase from opentelemetry.trace.span import format_span_id, format_trace_id @@ -61,15 +73,15 @@ def _make_client(self, service: str): def _default_span_attributes(self, service: str, operation: str): return { - SpanAttributes.RPC_SYSTEM: "aws-api", - SpanAttributes.RPC_SERVICE: service, - SpanAttributes.RPC_METHOD: operation, + rpc_attributes.RPC_SYSTEM: "aws-api", + rpc_attributes.RPC_SERVICE: service, + rpc_attributes.RPC_METHOD: operation, CLOUD_REGION: self.region, "retry_attempts": 0, - SpanAttributes.HTTP_STATUS_CODE: 200, + HTTP_STATUS_CODE: 200, # Some services like IAM or STS have a global endpoint and exclude specified region. - SpanAttributes.SERVER_ADDRESS: f"{service.lower()}.{'' if self.region == 'aws-global' else self.region + '.'}amazonaws.com", - SpanAttributes.SERVER_PORT: 443, + SERVER_ADDRESS: f"{service.lower()}.{'' if self.region == 'aws-global' else self.region + '.'}amazonaws.com", + SERVER_PORT: 443, } def assert_only_span(self): @@ -150,16 +162,16 @@ def test_exception(self): span = spans[0] expected = self._default_span_attributes("S3", "ListObjects") - expected.pop(SpanAttributes.HTTP_STATUS_CODE) + expected.pop(HTTP_STATUS_CODE) expected.pop("retry_attempts") self.assertEqual(expected, span.attributes) self.assertIs(span.status.status_code, trace_api.StatusCode.ERROR) self.assertEqual(1, len(span.events)) event = span.events[0] - self.assertIn(SpanAttributes.EXCEPTION_STACKTRACE, event.attributes) - self.assertIn(SpanAttributes.EXCEPTION_TYPE, event.attributes) - self.assertIn(SpanAttributes.EXCEPTION_MESSAGE, event.attributes) + self.assertIn(EXCEPTION_STACKTRACE, event.attributes) + self.assertIn(EXCEPTION_TYPE, event.attributes) + self.assertIn(EXCEPTION_MESSAGE, event.attributes) @mock_aws def test_s3_client(self): @@ -337,7 +349,7 @@ def test_sts_client(self): span = self.assert_only_span() expected = self._default_span_attributes("STS", "GetCallerIdentity") expected["aws.request_id"] = ANY - expected[SpanAttributes.SERVER_ADDRESS] = "sts.amazonaws.com" + expected[SERVER_ADDRESS] = "sts.amazonaws.com" # check for exact attribute set to make sure not to leak any sts secrets self.assertEqual(expected, dict(span.attributes)) @@ -515,8 +527,8 @@ def test_server_attributes(self): "EC2", "DescribeInstances", attributes={ - SpanAttributes.SERVER_ADDRESS: f"ec2.{self.region}.amazonaws.com", - SpanAttributes.SERVER_PORT: 443, + SERVER_ADDRESS: f"ec2.{self.region}.amazonaws.com", + SERVER_PORT: 443, }, ) self.memory_exporter.clear() @@ -528,8 +540,8 @@ def test_server_attributes(self): "IAM", "ListUsers", attributes={ - SpanAttributes.SERVER_ADDRESS: "iam.amazonaws.com", - SpanAttributes.SERVER_PORT: 443, + SERVER_ADDRESS: "iam.amazonaws.com", + SERVER_PORT: 443, CLOUD_REGION: "aws-global", }, ) @@ -552,7 +564,7 @@ def test_server_attributes_with_custom_endpoint(self): "S3", "ListBuckets", attributes={ - SpanAttributes.SERVER_ADDRESS: "proxy.amazon.org", - SpanAttributes.SERVER_PORT: 2025, + SERVER_ADDRESS: "proxy.amazon.org", + SERVER_PORT: 2025, }, ) diff --git a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_lambda.py b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_lambda.py index 4b2d359ac9..3ff8ca7f22 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_lambda.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_lambda.py @@ -27,7 +27,12 @@ _LambdaExtension, ) from opentelemetry.propagate import get_global_textmap, set_global_textmap -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv._incubating.attributes import rpc_attributes +from opentelemetry.semconv._incubating.attributes.faas_attributes import ( + FAAS_INVOKED_NAME, + FAAS_INVOKED_PROVIDER, + FAAS_INVOKED_REGION, +) from opentelemetry.test.mock_textmap import MockTextMapPropagator from opentelemetry.test.test_base import TestBase from opentelemetry.trace.span import Span @@ -73,22 +78,16 @@ def assert_span(self, operation: str) -> Span: self.assertEqual(1, len(spans)) span = spans[0] - self.assertEqual(operation, span.attributes[SpanAttributes.RPC_METHOD]) - self.assertEqual("Lambda", span.attributes[SpanAttributes.RPC_SERVICE]) - self.assertEqual("aws-api", span.attributes[SpanAttributes.RPC_SYSTEM]) + self.assertEqual(operation, span.attributes[rpc_attributes.RPC_METHOD]) + self.assertEqual("Lambda", span.attributes[rpc_attributes.RPC_SERVICE]) + self.assertEqual("aws-api", span.attributes[rpc_attributes.RPC_SYSTEM]) return span def assert_invoke_span(self, function_name: str) -> Span: span = self.assert_span("Invoke") - self.assertEqual( - "aws", span.attributes[SpanAttributes.FAAS_INVOKED_PROVIDER] - ) - self.assertEqual( - self.region, span.attributes[SpanAttributes.FAAS_INVOKED_REGION] - ) - self.assertEqual( - function_name, span.attributes[SpanAttributes.FAAS_INVOKED_NAME] - ) + self.assertEqual("aws", span.attributes[FAAS_INVOKED_PROVIDER]) + self.assertEqual(self.region, span.attributes[FAAS_INVOKED_REGION]) + self.assertEqual(function_name, span.attributes[FAAS_INVOKED_NAME]) return span @staticmethod @@ -182,6 +181,4 @@ def test_invoke_parse_arn(self): attributes = {} extension.extract_attributes(attributes) - self.assertEqual( - function_name, attributes[SpanAttributes.FAAS_INVOKED_NAME] - ) + self.assertEqual(function_name, attributes[FAAS_INVOKED_NAME]) From 61c8f24daf10aa752ae276b750b377b63aad0f66 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 29 Dec 2025 17:21:55 +0100 Subject: [PATCH 2/2] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 683f10a018..e965d6c1c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4058](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4058)) - `opentelemetry-instrumentation-django`: Replace SpanAttributes with semconv constants where applicable ([#4059](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4059)) +- `opentelemetry-instrumentation-botocore`: Replace SpanAttributes with semconv constants where applicable + ([#4063](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4063)) ## Version 1.39.0/0.60b0 (2025-12-03)