Skip to content

Commit 3dd31c5

Browse files
committed
feat: Add auto-instrumentation support for SecretsManager
1 parent 41e0987 commit 3dd31c5

File tree

5 files changed

+79
-1
lines changed

5 files changed

+79
-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
@@ -19,6 +19,7 @@
1919
AWS_REMOTE_RESOURCE_IDENTIFIER,
2020
AWS_REMOTE_RESOURCE_TYPE,
2121
AWS_REMOTE_SERVICE,
22+
AWS_SECRETSMANAGER_SECRET_ARN,
2223
AWS_SPAN_KIND,
2324
AWS_SQS_QUEUE_NAME,
2425
AWS_SQS_QUEUE_URL,
@@ -88,6 +89,7 @@
8889
_NORMALIZED_SQS_SERVICE_NAME: str = "AWS::SQS"
8990
_NORMALIZED_BEDROCK_SERVICE_NAME: str = "AWS::Bedrock"
9091
_NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: str = "AWS::BedrockRuntime"
92+
_NORMALIZED_SECRETSMANAGER_SERVICE_NAME: str = "AWS::SecretsManager"
9193
_DB_CONNECTION_STRING_TYPE: str = "DB::Connection"
9294

9395
# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
@@ -309,6 +311,7 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
309311
"Bedrock Agent": _NORMALIZED_BEDROCK_SERVICE_NAME,
310312
"Bedrock Agent Runtime": _NORMALIZED_BEDROCK_SERVICE_NAME,
311313
"Bedrock Runtime": _NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
314+
"Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
312315
}
313316
return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name)
314317
return service_name
@@ -416,6 +419,9 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
416419
elif is_key_present(span, GEN_AI_REQUEST_MODEL):
417420
remote_resource_type = _NORMALIZED_BEDROCK_SERVICE_NAME + "::Model"
418421
remote_resource_identifier = _escape_delimiters(span.attributes.get(GEN_AI_REQUEST_MODEL))
422+
elif is_key_present(span, AWS_SECRETSMANAGER_SECRET_ARN):
423+
remote_resource_type = _NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret"
424+
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SECRETSMANAGER_SECRET_ARN))
419425
elif is_db_span(span):
420426
remote_resource_type = _DB_CONNECTION_STRING_TYPE
421427
remote_resource_identifier = _get_db_connection(span)

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from amazon.opentelemetry.distro._aws_attribute_keys import (
77
AWS_KINESIS_STREAM_NAME,
8+
AWS_SECRETSMANAGER_SECRET_ARN,
89
AWS_SQS_QUEUE_NAME,
910
AWS_SQS_QUEUE_URL,
1011
)
@@ -16,8 +17,9 @@
1617
)
1718
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
1819
from opentelemetry.instrumentation.botocore.extensions.sqs import _SqsExtension
19-
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension
20+
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension, _BotoResultT
2021
from opentelemetry.semconv.trace import SpanAttributes
22+
from opentelemetry.trace.span import Span
2123

2224

2325
def _apply_botocore_instrumentation_patches() -> None:
@@ -29,6 +31,19 @@ def _apply_botocore_instrumentation_patches() -> None:
2931
_apply_botocore_s3_patch()
3032
_apply_botocore_sqs_patch()
3133
_apply_botocore_bedrock_patch()
34+
_apply_botocore_secretsmanager_patch()
35+
36+
37+
def _apply_botocore_secretsmanager_patch() -> None:
38+
"""Botocore instrumentation patch for SecretsManager
39+
40+
This patch adds an extension to the upstream's list of known extension for SecretsManager.
41+
Extensions allow for custom logic for adding service-specific information to spans, such as
42+
attributes. Specifically, we are adding logic to add the `aws.secretsmanager.secret.arn`
43+
attribute, to be used to generate RemoteTarget and achieve parity with the Java
44+
instrumentation.
45+
"""
46+
_KNOWN_EXTENSIONS["secretsmanager"] = _lazy_load(".", "_SecretsManagerExtension")
3247

3348

3449
def _apply_botocore_kinesis_patch() -> None:
@@ -108,6 +123,23 @@ def loader():
108123
# END The OpenTelemetry Authors code
109124

110125

126+
class _SecretsManagerExtension(_AwsSdkExtension):
127+
def extract_attributes(self, attributes: _AttributeMapT):
128+
"""
129+
SecretId can be secret name or secret arn, the function extracts attributes
130+
only if the SecretId parameter is provided as an arn which starts with
131+
`arn:aws:secretsmanager:`
132+
"""
133+
secret_id = self._call_context.params.get("SecretId")
134+
if secret_id and secret_id.startswith("arn:aws:secretsmanager:"):
135+
attributes[AWS_SECRETSMANAGER_SECRET_ARN] = secret_id
136+
137+
def on_success(self, span: Span, result: _BotoResultT):
138+
secret_arn = result.get("ARN")
139+
if secret_arn:
140+
span.set_attribute(AWS_SECRETSMANAGER_SECRET_ARN, secret_arn)
141+
142+
111143
class _S3Extension(_AwsSdkExtension):
112144
def extract_attributes(self, attributes: _AttributeMapT):
113145
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_SPAN_KIND,
2526
AWS_SQS_QUEUE_NAME,
2627
AWS_SQS_QUEUE_URL,
@@ -877,6 +878,7 @@ def test_normalize_remote_service_name_aws_sdk(self):
877878
self.validate_aws_sdk_service_normalization("Bedrock Agent", "AWS::Bedrock")
878879
self.validate_aws_sdk_service_normalization("Bedrock Agent Runtime", "AWS::Bedrock")
879880
self.validate_aws_sdk_service_normalization("Bedrock Runtime", "AWS::BedrockRuntime")
881+
self.validate_aws_sdk_service_normalization("Secrets Manager", "AWS::SecretsManager")
880882

881883
def validate_aws_sdk_service_normalization(self, service_name: str, expected_remote_service: str):
882884
self._mock_attribute([SpanAttributes.RPC_SYSTEM, SpanAttributes.RPC_SERVICE], ["aws-api", service_name])
@@ -1093,6 +1095,18 @@ def test_sdk_client_span_with_remote_resource_attributes(self):
10931095
self._validate_remote_resource_attributes("AWS::Bedrock::Model", "test.service_^^id")
10941096
self._mock_attribute([GEN_AI_REQUEST_MODEL], [None])
10951097

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

10981112
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
@@ -26,6 +26,7 @@
2626
_BEDROCK_KNOWLEDGEBASE_ID: str = "KnowledgeBaseId"
2727
_GEN_AI_SYSTEM: str = "aws_bedrock"
2828
_GEN_AI_REQUEST_MODEL: str = "genAiReuqestModelId"
29+
_SECRET_ARN: str = "arn:aws:secretsmanager:us-west-2:000000000000:secret:testSecret-ABCDEF"
2930

3031
# Patch names
3132
GET_DISTRIBUTION_PATCH: str = (
@@ -141,6 +142,9 @@ def _test_unpatched_botocore_instrumentation(self):
141142
# BedrockRuntime
142143
self.assertFalse("bedrock-runtime" in _KNOWN_EXTENSIONS, "Upstream has added a bedrock-runtime extension")
143144

145+
# SecretsManager
146+
self.assertFalse("secretsmanager" in _KNOWN_EXTENSIONS, "Upstream has added a SecretsManager extension")
147+
144148
def _test_unpatched_gevent_instrumentation(self):
145149
self.assertFalse(gevent.monkey.is_module_patched("os"), "gevent os module has been patched")
146150
self.assertFalse(gevent.monkey.is_module_patched("thread"), "gevent thread module has been patched")
@@ -200,6 +204,15 @@ def _test_patched_botocore_instrumentation(self):
200204
self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM)
201205
self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], _GEN_AI_REQUEST_MODEL)
202206

207+
# SecretsManager
208+
self.assertTrue("secretsmanager" in _KNOWN_EXTENSIONS)
209+
secretsmanager_attributes: Dict[str, str] = _do_extract_secretsmanager_attributes()
210+
self.assertTrue("aws.secretsmanager.secret.arn" in secretsmanager_attributes)
211+
self.assertEqual(secretsmanager_attributes["aws.secretsmanager.secret.arn"], _SECRET_ARN)
212+
secretsmanager_success_attributes: Dict[str, str] = _do_on_success_secretsmanager()
213+
self.assertTrue("aws.secretsmanager.secret.arn" in secretsmanager_success_attributes)
214+
self.assertEqual(secretsmanager_success_attributes["aws.secretsmanager.secret.arn"], _SECRET_ARN)
215+
203216
def _test_patched_gevent_os_ssl_instrumentation(self):
204217
# Only ssl and os module should have been patched since the environment variable was set to 'os, ssl'
205218
self.assertTrue(gevent.monkey.is_module_patched("ssl"), "gevent ssl module has not been patched")
@@ -358,6 +371,18 @@ def _do_on_success_bedrock(service, operation=None) -> Dict[str, str]:
358371
return _do_on_success(service, result, operation)
359372

360373

374+
def _do_extract_secretsmanager_attributes() -> Dict[str, str]:
375+
service_name: str = "secretsmanager"
376+
params: Dict[str, str] = {"SecretId": _SECRET_ARN}
377+
return _do_extract_attributes(service_name, params)
378+
379+
380+
def _do_on_success_secretsmanager() -> Dict[str, str]:
381+
service_name: str = "secretsmanager"
382+
result: Dict[str, Any] = {"ARN": _SECRET_ARN}
383+
return _do_on_success(service_name, result)
384+
385+
361386
def _do_extract_attributes(service_name: str, params: Dict[str, Any], operation: str = None) -> Dict[str, str]:
362387
mock_call_context: MagicMock = MagicMock()
363388
mock_call_context.params = params

0 commit comments

Comments
 (0)