Skip to content

Commit 4d85bc6

Browse files
committed
feat: Add SecretsManager Secret ARN to Span Data
1 parent 8c23cec commit 4d85bc6

File tree

5 files changed

+77
-1
lines changed

5 files changed

+77
-1
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
@@ -23,3 +23,4 @@
2323
AWS_BEDROCK_KNOWLEDGE_BASE_ID: str = "aws.bedrock.knowledge_base.id"
2424
AWS_BEDROCK_AGENT_ID: str = "aws.bedrock.agent.id"
2525
AWS_BEDROCK_GUARDRAIL_ID: str = "aws.bedrock.guardrail.id"
26+
AWS_SECRETSMANAGER_SECRET_ARN: str = "aws.secretsmanager.secret.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
@@ -18,6 +18,7 @@
1818
AWS_REMOTE_RESOURCE_IDENTIFIER,
1919
AWS_REMOTE_RESOURCE_TYPE,
2020
AWS_REMOTE_SERVICE,
21+
AWS_SECRETSMANAGER_SECRET_ARN,
2122
AWS_SNS_TOPIC_ARN,
2223
AWS_SPAN_KIND,
2324
AWS_SQS_QUEUE_NAME,
@@ -89,6 +90,7 @@
8990
_NORMALIZED_SNS_SERVICE_NAME: str = "AWS::SNS"
9091
_NORMALIZED_BEDROCK_SERVICE_NAME: str = "AWS::Bedrock"
9192
_NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: str = "AWS::BedrockRuntime"
93+
_NORMALIZED_SECRETSMANAGER_SERVICE_NAME: str = "AWS::SecretsManager"
9294
_DB_CONNECTION_STRING_TYPE: str = "DB::Connection"
9395

9496
# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
@@ -310,6 +312,7 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
310312
"Bedrock Agent": _NORMALIZED_BEDROCK_SERVICE_NAME,
311313
"Bedrock Agent Runtime": _NORMALIZED_BEDROCK_SERVICE_NAME,
312314
"Bedrock Runtime": _NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
315+
"Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
313316
}
314317
return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name)
315318
return service_name
@@ -412,6 +415,9 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
412415
elif is_key_present(span, AWS_SNS_TOPIC_ARN):
413416
remote_resource_type = _NORMALIZED_SNS_SERVICE_NAME + "::Topic"
414417
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SNS_TOPIC_ARN))
418+
elif is_key_present(span, AWS_SECRETSMANAGER_SECRET_ARN):
419+
remote_resource_type = _NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret"
420+
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SECRETSMANAGER_SECRET_ARN))
415421
elif is_db_span(span):
416422
remote_resource_type = _DB_CONNECTION_STRING_TYPE
417423
remote_resource_identifier = _get_db_connection(span)

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
1818
from opentelemetry.instrumentation.botocore.extensions.sns import _SnsExtension
1919
from opentelemetry.instrumentation.botocore.extensions.sqs import _SqsExtension
20-
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension
20+
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension, _BotoResultT
2121
from opentelemetry.semconv.trace import SpanAttributes
22+
from opentelemetry.trace.span import Span
2223

2324

2425
def _apply_botocore_instrumentation_patches() -> None:
@@ -31,6 +32,18 @@ def _apply_botocore_instrumentation_patches() -> None:
3132
_apply_botocore_sqs_patch()
3233
_apply_botocore_bedrock_patch()
3334
_apply_botocore_sns_patch()
35+
_apply_botocore_secretsmanager_patch()
36+
37+
38+
def _apply_botocore_secretsmanager_patch() -> None:
39+
"""Botocore instrumentation patch for SecretsManager
40+
41+
This patch adds an extension to the upstream's list of known extensions for SecretsManager.
42+
Extensions allow for custom logic for adding service-specific information to spans,
43+
such as attributes. Specifically, we are adding logic to add the
44+
AWS_SECRETSMANAGER_SECRET_ARN attribute.
45+
"""
46+
_KNOWN_EXTENSIONS["secretsmanager"] = _lazy_load(".", "_SecretsManagerExtension")
3447

3548

3649
def _apply_botocore_sns_patch() -> None:
@@ -132,6 +145,23 @@ def loader():
132145
# END The OpenTelemetry Authors code
133146

134147

148+
class _SecretsManagerExtension(_AwsSdkExtension):
149+
def extract_attributes(self, attributes: _AttributeMapT):
150+
"""
151+
SecretId can be secret name or secret arn, the function extracts attributes only if
152+
the SecretId parameter is provided as an arn which starts with
153+
'arn:aws:secretsmanager:'.
154+
"""
155+
secret_id = self._call_context.params.get("SecretId")
156+
if secret_id and secret_id.startswith("arn:aws:secretsmanager:"):
157+
attributes["aws.secretsmanager.secret.arn"] = secret_id
158+
159+
def on_success(self, span: Span, result: _BotoResultT):
160+
secret_arn = result.get("ARN")
161+
if secret_arn:
162+
span.set_attribute("aws.secretsmanager.secret.arn", secret_arn)
163+
164+
135165
class _S3Extension(_AwsSdkExtension):
136166
def extract_attributes(self, attributes: _AttributeMapT):
137167
bucket_name = self._call_context.params.get("Bucket")

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
AWS_REMOTE_RESOURCE_IDENTIFIER,
2222
AWS_REMOTE_RESOURCE_TYPE,
2323
AWS_REMOTE_SERVICE,
24+
AWS_SECRETSMANAGER_SECRET_ARN,
2425
AWS_SNS_TOPIC_ARN,
2526
AWS_SPAN_KIND,
2627
AWS_SQS_QUEUE_NAME,
@@ -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("SNS", "AWS::SNS")
883+
self.validate_aws_sdk_service_normalization("Secrets Manager", "AWS::SecretsManager")
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])
@@ -1090,6 +1092,18 @@ def test_sdk_client_span_with_remote_resource_attributes(self):
10901092
self._validate_remote_resource_attributes("AWS::Bedrock::Model", "test.service_^^id")
10911093
self._mock_attribute([GEN_AI_REQUEST_MODEL], [None])
10921094

1095+
# Validate behaviour of AWS_SECRETSMANAGER_SECRET_ARN attribute, then remove it
1096+
self._mock_attribute(
1097+
[AWS_SECRETSMANAGER_SECRET_ARN],
1098+
["arn:aws:secretsmanager:us-east-1:123456789012:secret:secret_name-lERW9H"],
1099+
keys,
1100+
values,
1101+
)
1102+
self._validate_remote_resource_attributes(
1103+
"AWS::SecretsManager::Secret", "arn:aws:secretsmanager:us-east-1:123456789012:secret:secret_name-lERW9H"
1104+
)
1105+
self._mock_attribute([AWS_SECRETSMANAGER_SECRET_ARN], [None])
1106+
10931107
self._mock_attribute([SpanAttributes.RPC_SYSTEM], [None])
10941108

10951109
def test_client_db_span_with_remote_resource_attributes(self):

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

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

3132
# Patch names
3233
GET_DISTRIBUTION_PATCH: str = (
@@ -147,6 +148,9 @@ def _test_unpatched_botocore_instrumentation(self):
147148
# BedrockRuntime
148149
self.assertFalse("bedrock-runtime" in _KNOWN_EXTENSIONS, "Upstream has added a bedrock-runtime extension")
149150

151+
# SecretsManager
152+
self.assertFalse("secretsmanager" in _KNOWN_EXTENSIONS, "Upstream has added a SecretsManager extension")
153+
150154
def _test_unpatched_gevent_instrumentation(self):
151155
self.assertFalse(gevent.monkey.is_module_patched("os"), "gevent os module has been patched")
152156
self.assertFalse(gevent.monkey.is_module_patched("thread"), "gevent thread module has been patched")
@@ -212,6 +216,15 @@ def _test_patched_botocore_instrumentation(self):
212216
self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM)
213217
self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], _GEN_AI_REQUEST_MODEL)
214218

219+
# SecretsManager
220+
self.assertTrue("secretsmanager" in _KNOWN_EXTENSIONS)
221+
secretsmanager_attributes: Dict[str, str] = _do_extract_secretsmanager_attributes()
222+
self.assertTrue("aws.secretsmanager.secret.arn" in secretsmanager_attributes)
223+
self.assertEqual(secretsmanager_attributes["aws.secretsmanager.secret.arn"], _SECRET_ARN)
224+
secretsmanager_success_attributes: Dict[str, str] = _do_on_success_secretsmanager()
225+
self.assertTrue("aws.secretsmanager.secret.arn" in secretsmanager_success_attributes)
226+
self.assertEqual(secretsmanager_success_attributes["aws.secretsmanager.secret.arn"], _SECRET_ARN)
227+
215228
def _test_patched_gevent_os_ssl_instrumentation(self):
216229
# Only ssl and os module should have been patched since the environment variable was set to 'os, ssl'
217230
self.assertTrue(gevent.monkey.is_module_patched("ssl"), "gevent ssl module has not been patched")
@@ -365,6 +378,18 @@ def _do_on_success_bedrock(service, operation=None) -> Dict[str, str]:
365378
return _do_on_success(service, result, operation)
366379

367380

381+
def _do_extract_secretsmanager_attributes() -> Dict[str, str]:
382+
service_name: str = "secretsmanager"
383+
params: Dict[str, str] = {"SecretId": _SECRET_ARN}
384+
return _do_extract_attributes(service_name, params)
385+
386+
387+
def _do_on_success_secretsmanager() -> Dict[str, str]:
388+
service_name: str = "secretsmanager"
389+
result: Dict[str, Any] = {"ARN": _SECRET_ARN}
390+
return _do_on_success(service_name, result)
391+
392+
368393
def _do_extract_attributes(service_name: str, params: Dict[str, Any], operation: str = None) -> Dict[str, str]:
369394
mock_call_context: MagicMock = MagicMock()
370395
mock_call_context.params = params

0 commit comments

Comments
 (0)