Skip to content

Commit 6fcf4cc

Browse files
committed
feat: Add auto-instrumentation support for SNS
1 parent 3dd31c5 commit 6fcf4cc

File tree

5 files changed

+56
-0
lines changed

5 files changed

+56
-0
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_attribute_keys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
AWS_BEDROCK_AGENT_ID: str = "aws.bedrock.agent.id"
2525
AWS_BEDROCK_GUARDRAIL_ID: str = "aws.bedrock.guardrail.id"
2626
AWS_SECRETSMANAGER_SECRET_ARN: str = "aws.secretsmanager.secret.arn"
27+
AWS_SNS_TOPIC_ARN: str = "aws.sns.topic.arn"

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
AWS_REMOTE_RESOURCE_TYPE,
2121
AWS_REMOTE_SERVICE,
2222
AWS_SECRETSMANAGER_SECRET_ARN,
23+
AWS_SNS_TOPIC_ARN,
2324
AWS_SPAN_KIND,
2425
AWS_SQS_QUEUE_NAME,
2526
AWS_SQS_QUEUE_URL,
@@ -90,6 +91,7 @@
9091
_NORMALIZED_BEDROCK_SERVICE_NAME: str = "AWS::Bedrock"
9192
_NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: str = "AWS::BedrockRuntime"
9293
_NORMALIZED_SECRETSMANAGER_SERVICE_NAME: str = "AWS::SecretsManager"
94+
_NORMALIZED_SNS_SERVICE_NAME: str = "AWS::SNS"
9395
_DB_CONNECTION_STRING_TYPE: str = "DB::Connection"
9496

9597
# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
@@ -312,6 +314,7 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
312314
"Bedrock Agent Runtime": _NORMALIZED_BEDROCK_SERVICE_NAME,
313315
"Bedrock Runtime": _NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
314316
"Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
317+
"SNS": _NORMALIZED_SNS_SERVICE_NAME,
315318
}
316319
return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name)
317320
return service_name
@@ -422,6 +425,9 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
422425
elif is_key_present(span, AWS_SECRETSMANAGER_SECRET_ARN):
423426
remote_resource_type = _NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret"
424427
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SECRETSMANAGER_SECRET_ARN))
428+
elif is_key_present(span, AWS_SNS_TOPIC_ARN):
429+
remote_resource_type = _NORMALIZED_SNS_SERVICE_NAME + "::Topic"
430+
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SNS_TOPIC_ARN))
425431
elif is_db_span(span):
426432
remote_resource_type = _DB_CONNECTION_STRING_TYPE
427433
remote_resource_identifier = _get_db_connection(span)

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_botocore_patches.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from amazon.opentelemetry.distro._aws_attribute_keys import (
77
AWS_KINESIS_STREAM_NAME,
88
AWS_SECRETSMANAGER_SECRET_ARN,
9+
AWS_SNS_TOPIC_ARN,
910
AWS_SQS_QUEUE_NAME,
1011
AWS_SQS_QUEUE_URL,
1112
)
@@ -16,6 +17,7 @@
1617
_BedrockRuntimeExtension,
1718
)
1819
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
20+
from opentelemetry.instrumentation.botocore.extensions.sns import _SnsExtension
1921
from opentelemetry.instrumentation.botocore.extensions.sqs import _SqsExtension
2022
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension, _BotoResultT
2123
from opentelemetry.semconv.trace import SpanAttributes
@@ -32,8 +34,32 @@ def _apply_botocore_instrumentation_patches() -> None:
3234
_apply_botocore_sqs_patch()
3335
_apply_botocore_bedrock_patch()
3436
_apply_botocore_secretsmanager_patch()
37+
_apply_botocore_sns_patch()
3538

3639

40+
def _apply_botocore_sns_patch() -> None:
41+
"""Botocore instrumentation patch for SNS
42+
43+
This patch adds an extension to the upstream's list of known extensions for SNS.
44+
Extensions allow for custom logic for adding service-specific information to
45+
spans, such as attributes. Specifically, we are adding logic to add the
46+
`aws.sns.topic.arn` attribute, to be used to generate RemoteTarget and achieve
47+
parity with the Java instrumentation.
48+
49+
Sidenote: There exists SpanAttributes.MESSAGING_DESTINATION_NAME in the upstream
50+
logic that we could re-purpose here. We do not use it here to maintain consistent
51+
naming patterns with other AWS resources.
52+
"""
53+
old_extract_attributes = _SnsExtension.extract_attributes
54+
55+
def patch_extract_attributes(self, attributes: _AttributeMapT):
56+
old_extract_attributes(self, attributes)
57+
topic_arn = self._call_context.params.get("TopicArn")
58+
if topic_arn:
59+
attributes[AWS_SNS_TOPIC_ARN] = topic_arn
60+
61+
_SnsExtension.extract_attributes = patch_extract_attributes
62+
3763
def _apply_botocore_secretsmanager_patch() -> None:
3864
"""Botocore instrumentation patch for SecretsManager
3965

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_metric_attribute_generator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
AWS_REMOTE_RESOURCE_TYPE,
2323
AWS_REMOTE_SERVICE,
2424
AWS_SECRETSMANAGER_SECRET_ARN,
25+
AWS_SNS_TOPIC_ARN,
2526
AWS_SPAN_KIND,
2627
AWS_SQS_QUEUE_NAME,
2728
AWS_SQS_QUEUE_URL,
@@ -879,6 +880,7 @@ def test_normalize_remote_service_name_aws_sdk(self):
879880
self.validate_aws_sdk_service_normalization("Bedrock Agent Runtime", "AWS::Bedrock")
880881
self.validate_aws_sdk_service_normalization("Bedrock Runtime", "AWS::BedrockRuntime")
881882
self.validate_aws_sdk_service_normalization("Secrets Manager", "AWS::SecretsManager")
883+
self.validate_aws_sdk_service_normalization("SNS", "AWS::SNS")
882884

883885
def validate_aws_sdk_service_normalization(self, service_name: str, expected_remote_service: str):
884886
self._mock_attribute([SpanAttributes.RPC_SYSTEM, SpanAttributes.RPC_SERVICE], ["aws-api", service_name])
@@ -1107,6 +1109,11 @@ def test_sdk_client_span_with_remote_resource_attributes(self):
11071109
)
11081110
self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None])
11091111

1112+
# Validate behaviour of AWS_SNS_TOPIC_ARN attribute, then remove it.
1113+
self._mock_attribute([AWS_SNS_TOPIC_ARN], ["arn:aws:sns:us-west-2:012345678901:test_topic"], keys, values)
1114+
self._validate_remote_resource_attributes("AWS::SNS::Topic", "arn:aws:sns:us-west-2:012345678901:test_topic")
1115+
self._mock_attribute([AWS_SNS_TOPIC_ARN], [None])
1116+
11101117
self._mock_attribute([SpanAttributes.RPC_SYSTEM], [None])
11111118

11121119
def test_client_db_span_with_remote_resource_attributes(self):

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
_GEN_AI_SYSTEM: str = "aws_bedrock"
2828
_GEN_AI_REQUEST_MODEL: str = "genAiReuqestModelId"
2929
_SECRET_ARN: str = "arn:aws:secretsmanager:us-west-2:000000000000:secret:testSecret-ABCDEF"
30+
_TOPIC_ARN: str = "topicArn"
3031

3132
# Patch names
3233
GET_DISTRIBUTION_PATCH: str = (
@@ -145,6 +146,9 @@ def _test_unpatched_botocore_instrumentation(self):
145146
# SecretsManager
146147
self.assertFalse("secretsmanager" in _KNOWN_EXTENSIONS, "Upstream has added a SecretsManager extension")
147148

149+
# SNS
150+
self.assertTrue("sns" in _KNOWN_EXTENSIONS, "Upstream has removed the SNS extension")
151+
148152
def _test_unpatched_gevent_instrumentation(self):
149153
self.assertFalse(gevent.monkey.is_module_patched("os"), "gevent os module has been patched")
150154
self.assertFalse(gevent.monkey.is_module_patched("thread"), "gevent thread module has been patched")
@@ -213,6 +217,12 @@ def _test_patched_botocore_instrumentation(self):
213217
self.assertTrue("aws.secretsmanager.secret.arn" in secretsmanager_success_attributes)
214218
self.assertEqual(secretsmanager_success_attributes["aws.secretsmanager.secret.arn"], _SECRET_ARN)
215219

220+
# SNS
221+
self.assertTrue("sns" in _KNOWN_EXTENSIONS)
222+
sns_attributes: Dict[str, str] = _do_extract_sns_attributes()
223+
self.assertTrue("aws.sns.topic.arn" in sns_attributes)
224+
self.assertEqual(sns_attributes["aws.sns.topic.arn"], _TOPIC_ARN)
225+
216226
def _test_patched_gevent_os_ssl_instrumentation(self):
217227
# Only ssl and os module should have been patched since the environment variable was set to 'os, ssl'
218228
self.assertTrue(gevent.monkey.is_module_patched("ssl"), "gevent ssl module has not been patched")
@@ -383,6 +393,12 @@ def _do_on_success_secretsmanager() -> Dict[str, str]:
383393
return _do_on_success(service_name, result)
384394

385395

396+
def _do_extract_sns_attributes() -> Dict[str, str]:
397+
service_name: str = "sns"
398+
params: Dict[str, str] = {"TopicArn": _TOPIC_ARN}
399+
return _do_extract_attributes(service_name, params)
400+
401+
386402
def _do_extract_attributes(service_name: str, params: Dict[str, Any], operation: str = None) -> Dict[str, str]:
387403
mock_call_context: MagicMock = MagicMock()
388404
mock_call_context.params = params

0 commit comments

Comments
 (0)