Skip to content

Commit 7745ce0

Browse files
liustveADOT Patch workflow
andauthored
feat: Add botocore instrumentation extension for Bedrock AgentCore services (#490)
*Description of changes:* Patches botocore instrumentor to add span attributes for Bedrock AgentCore control and data plane calls. - Added `_BedrockAgentCoreExtension` to extract telemetry attributes from API parameters - Registered extensions for `bedrock-agentcore` (data plane) and `bedrock-agentcore-control` (control plane) - Added attribute constants for runtime, browser, code interpreter, gateway, memory, and identity resources New semantic conventions based off this PR: open-telemetry/semantic-conventions#2886 Manually tested verified span attributes examples: ``` { "name": "Bedrock AgentCore.InvokeAgentRuntime", "context": { "trace_id": "0x68e9815d69c6335ba2be2c3a0dc9460d", "span_id": "0x7b65fdc962dfe773", "trace_state": "[]" }, "kind": "SpanKind.CLIENT", "parent_id": null, "start_time": "2025-10-10T21:57:49.492869Z", "end_time": "2025-10-10T21:57:50.070664Z", "status": { "status_code": "UNSET" }, "attributes": { "rpc.system": "aws-api", "rpc.service": "Bedrock AgentCore", "rpc.method": "InvokeAgentRuntime", "aws.region": "us-east-1", "server.address": "bedrock-agentcore.us-east-1.amazonaws.com", "server.port": 443, "aws.auth.region": "us-east-1", "aws.auth.account.access_key": "", "aws.bedrock.agentcore.runtime.arn": "arn:aws:bedrock-agentcore:us-east-1:571600841604:runtime/completeAgent-w8slyU6q5M", "aws.request_id": "a4483186-85ba-4879-b83e-99562da78376", "retry_attempts": 0, "http.status_code": 200 }, "events": [], "links": [], "resource": { "attributes": { "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.33.1", "service.name": "unknown_service", "telemetry.auto.version": "0.12.1.dev0-aws", "aws.local.service": "UnknownService" }, "schema_url": "" } } ``` ``` { "name": "Bedrock AgentCore.InvokeCodeInterpreter", "context": { "trace_id": "0x68e98228e901936ec8fe3d76839e3da3", "span_id": "0x1751acb67a5b3073", "trace_state": "[]" }, "kind": "SpanKind.CLIENT", "parent_id": null, "start_time": "2025-10-10T22:01:12.522286Z", "end_time": "2025-10-10T22:01:12.691952Z", "status": { "status_code": "UNSET" }, "attributes": { "rpc.system": "aws-api", "rpc.service": "Bedrock AgentCore", "rpc.method": "InvokeCodeInterpreter", "aws.region": "us-east-1", "server.address": "bedrock-agentcore.us-east-1.amazonaws.com", "server.port": 443, "aws.auth.region": "us-east-1", "gen_ai.code_interpreter.id": "agentCodeInterpreter-m9Mvuwkg6j", "aws.request_id": "b9fd2327-edc4-413d-89d7-715ae836ec64", "retry_attempts": 0, "http.status_code": 200 }, "events": [], "links": [], "resource": { "attributes": { "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.33.1", "service.name": "unknown_service", "telemetry.auto.version": "0.12.1.dev0-aws", "aws.local.service": "UnknownService" }, "schema_url": "" } } ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Co-authored-by: ADOT Patch workflow <[email protected]>
1 parent 54acafb commit 7745ce0

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ For any change that affects end users of this package, please add an entry under
1111
If your change does not need a CHANGELOG entry, add the "skip changelog" label to your PR.
1212

1313
## Unreleased
14+
- Add botocore instrumentation extension for Bedrock AgentCore services with span attributes
15+
([#490](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/490))

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
# TODO:Move to Semantic Conventions when these attributes are added.
2121
AWS_AUTH_ACCESS_KEY: str = "aws.auth.account.access_key"
2222
AWS_AUTH_REGION: str = "aws.auth.region"
23+
AWS_AUTH_CREDENTIAL_PROVIDER_ARN: str = "aws.auth.credential_provider.arn"
24+
AWS_GATEWAY_TARGET_ID = "aws.gateway.target.id"
2325
AWS_SQS_QUEUE_URL: str = "aws.sqs.queue.url"
2426
AWS_SQS_QUEUE_NAME: str = "aws.sqs.queue.name"
2527
AWS_KINESIS_STREAM_ARN: str = "aws.kinesis.stream.arn"
@@ -41,3 +43,10 @@
4143
AWS_REMOTE_RESOURCE_ACCOUNT_ID: str = "aws.remote.resource.account.id"
4244
AWS_REMOTE_RESOURCE_REGION: str = "aws.remote.resource.region"
4345
AWS_SERVICE_TYPE: str = "aws.service.type"
46+
AWS_BEDROCK_AGENTCORE_RUNTIME_ARN: str = "aws.bedrock.agentcore.runtime.arn"
47+
AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN: str = "aws.bedrock.agentcore.runtime_endpoint.arn"
48+
AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN: str = "aws.bedrock.agentcore.code_interpreter.arn"
49+
AWS_BEDROCK_AGENTCORE_BROWSER_ARN: str = "aws.bedrock.agentcore.browser.arn"
50+
AWS_BEDROCK_AGENTCORE_MEMORY_ARN: str = "aws.bedrock.agentcore.memory.arn"
51+
AWS_BEDROCK_AGENTCORE_GATEWAY_ARN: str = "aws.bedrock.agentcore.gateway.arn"
52+
AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN: str = "aws.bedrock.agentcore.identity.workload_identity.arn"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from typing import Any, Dict
4+
5+
from amazon.opentelemetry.distro._aws_attribute_keys import (
6+
AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
7+
AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
8+
AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN,
9+
AWS_BEDROCK_AGENTCORE_GATEWAY_ARN,
10+
AWS_BEDROCK_AGENTCORE_MEMORY_ARN,
11+
AWS_BEDROCK_AGENTCORE_RUNTIME_ARN,
12+
AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN,
13+
AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
14+
AWS_GATEWAY_TARGET_ID,
15+
)
16+
from opentelemetry.instrumentation.botocore.extensions.types import (
17+
_AttributeMapT,
18+
_AwsSdkExtension,
19+
_BotocoreInstrumentorContext,
20+
_BotoResultT,
21+
)
22+
from opentelemetry.trace.span import Span
23+
24+
GEN_AI_RUNTIME_ID = "gen_ai.runtime.id"
25+
GEN_AI_BROWSER_ID = "gen_ai.browser.id"
26+
GEN_AI_CODE_INTERPRETER_ID = "gen_ai.code_interpreter.id"
27+
GEN_AI_MEMORY_ID = "gen_ai.memory.id"
28+
GEN_AI_GATEWAY_ID = "gen_ai.gateway.id"
29+
30+
# Mapping of flattened JSON paths to attribute keys
31+
_ATTRIBUTE_MAPPING = {
32+
"agentRuntimeArn": AWS_BEDROCK_AGENTCORE_RUNTIME_ARN,
33+
"agentRuntimeEndpointArn": AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN,
34+
"agentRuntimeId": GEN_AI_RUNTIME_ID,
35+
"browserArn": AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
36+
"browserId": GEN_AI_BROWSER_ID,
37+
"browserIdentifier": GEN_AI_BROWSER_ID,
38+
"codeInterpreterArn": AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN,
39+
"codeInterpreterId": GEN_AI_CODE_INTERPRETER_ID,
40+
"codeInterpreterIdentifier": GEN_AI_CODE_INTERPRETER_ID,
41+
"gatewayArn": AWS_BEDROCK_AGENTCORE_GATEWAY_ARN,
42+
"gatewayId": GEN_AI_GATEWAY_ID,
43+
"gatewayIdentifier": GEN_AI_GATEWAY_ID,
44+
"targetId": AWS_GATEWAY_TARGET_ID,
45+
"memory.arn": AWS_BEDROCK_AGENTCORE_MEMORY_ARN,
46+
"memory.id": GEN_AI_MEMORY_ID,
47+
"memoryId": GEN_AI_MEMORY_ID,
48+
"credentialProviderArn": AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
49+
"workloadIdentityArn": AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
50+
"workloadIdentityDetails.workloadIdentityArn": AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
51+
}
52+
53+
54+
class _BedrockAgentCoreExtension(_AwsSdkExtension):
55+
def extract_attributes(self, attributes: _AttributeMapT):
56+
extracted_attrs = self._extract_attributes(self._call_context.params)
57+
attributes.update(extracted_attrs)
58+
59+
def on_success(
60+
self,
61+
span: Span,
62+
result: _BotoResultT,
63+
instrumentor_context: _BotocoreInstrumentorContext,
64+
):
65+
if span is None or not span.is_recording():
66+
return
67+
68+
extracted_attrs = self._extract_attributes(result)
69+
for attr_name, attr_value in extracted_attrs.items():
70+
span.set_attribute(attr_name, attr_value)
71+
72+
@staticmethod
73+
def _extract_attributes(params: Dict[str, Any]):
74+
"""Extracts all Bedrock AgentCore attributes using mapping-based traversal"""
75+
attrs = {}
76+
for path, attr_key in _ATTRIBUTE_MAPPING.items():
77+
value = _BedrockAgentCoreExtension._get_nested_value(params, path)
78+
if value:
79+
attrs[attr_key] = value
80+
return attrs
81+
82+
@staticmethod
83+
def _get_nested_value(data: Dict[str, Any], path: str):
84+
"""Get value from nested dictionary using dot notation path"""
85+
keys = path.split(".")
86+
value = data
87+
for key in keys:
88+
if isinstance(value, dict) and key in value:
89+
value = value[key]
90+
else:
91+
return None
92+
return value

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
AWS_STEPFUNCTIONS_ACTIVITY_ARN,
2424
AWS_STEPFUNCTIONS_STATEMACHINE_ARN,
2525
)
26+
from amazon.opentelemetry.distro.patches._bedrock_agentcore_patches import ( # noqa # pylint: disable=unused-import
27+
_BedrockAgentCoreExtension,
28+
)
2629
from amazon.opentelemetry.distro.patches._bedrock_patches import ( # noqa # pylint: disable=unused-import
2730
_BedrockAgentExtension,
2831
_BedrockAgentRuntimeExtension,
@@ -247,6 +250,10 @@ def _apply_botocore_bedrock_patch() -> None: # pylint: disable=too-many-stateme
247250
_KNOWN_EXTENSIONS["bedrock"] = _lazy_load(".", "_BedrockExtension")
248251
_KNOWN_EXTENSIONS["bedrock-agent"] = _lazy_load(".", "_BedrockAgentExtension")
249252
_KNOWN_EXTENSIONS["bedrock-agent-runtime"] = _lazy_load(".", "_BedrockAgentRuntimeExtension")
253+
_KNOWN_EXTENSIONS["bedrock-agentcore"] = _lazy_load(".._bedrock_agentcore_patches", "_BedrockAgentCoreExtension")
254+
_KNOWN_EXTENSIONS["bedrock-agentcore-control"] = _lazy_load(
255+
".._bedrock_agentcore_patches", "_BedrockAgentCoreExtension"
256+
)
250257

251258
# TODO: The following code is to patch bedrock-runtime bugs that are fixed in
252259
# opentelemetry-instrumentation-botocore==0.56b0 in these PRs:

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py renamed to aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/patches/test_instrumentation_patch.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@
1010

1111
import opentelemetry.sdk.extension.aws.resource.ec2 as ec2_resource
1212
import opentelemetry.sdk.extension.aws.resource.eks as eks_resource
13+
from amazon.opentelemetry.distro._aws_attribute_keys import (
14+
AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
15+
AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
16+
AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN,
17+
AWS_BEDROCK_AGENTCORE_GATEWAY_ARN,
18+
AWS_BEDROCK_AGENTCORE_MEMORY_ARN,
19+
AWS_BEDROCK_AGENTCORE_RUNTIME_ARN,
20+
AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN,
21+
AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
22+
AWS_GATEWAY_TARGET_ID,
23+
)
24+
from amazon.opentelemetry.distro.patches._bedrock_agentcore_patches import (
25+
GEN_AI_BROWSER_ID,
26+
GEN_AI_CODE_INTERPRETER_ID,
27+
GEN_AI_GATEWAY_ID,
28+
GEN_AI_MEMORY_ID,
29+
GEN_AI_RUNTIME_ID,
30+
)
1331
from amazon.opentelemetry.distro.patches._instrumentation_patch import (
1432
AWS_GEVENT_PATCH_MODULES,
1533
apply_instrumentation_patches,
@@ -38,6 +56,24 @@
3856
_LAMBDA_FUNCTION_NAME: str = "lambdaFunctionName"
3957
_LAMBDA_SOURCE_MAPPING_ID: str = "lambdaEventSourceMappingID"
4058
_TABLE_ARN: str = "arn:aws:dynamodb:us-west-2:123456789012:table/testTable"
59+
_AGENTCORE_RUNTIME_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/test-runtime-123"
60+
_AGENTCORE_RUNTIME_ENDPOINT_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime-endpoint/test-endpoint"
61+
_AGENTCORE_RUNTIME_ID: str = "test-runtime-123"
62+
_AGENTCORE_BROWSER_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:browser/testBrowser-1234567890"
63+
_AGENTCORE_BROWSER_ID: str = "testBrowser-1234567890"
64+
_AGENTCORE_CODE_INTERPRETER_ARN: str = (
65+
"arn:aws:bedrock-agentcore:us-east-1:123456789012:code-interpreter/testCodeInt-1234567890"
66+
)
67+
_AGENTCORE_CODE_INTERPRETER_ID: str = "testCodeInt-1234567890"
68+
_AGENTCORE_GATEWAY_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/agentGateway-123456789"
69+
_AGENTCORE_GATEWAY_ID: str = "agentGateway-123456789"
70+
_AGENTCORE_TARGET_ID: str = "target-123456789"
71+
_AGENTCORE_MEMORY_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/agentMemory-123456789"
72+
_AGENTCORE_MEMORY_ID: str = "agentMemory-123456789"
73+
_AGENTCORE_CREDENTIAL_PROVIDER_ARN: str = (
74+
"arn:aws:acps:us-east-1:123456789012:token-vault/test-vault/apikeycredentialprovider/test-provider"
75+
)
76+
_AGENTCORE_WORKLOAD_IDENTITY_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:workload-identity/test-wi"
4177

4278
# Patch names
4379
IMPORTLIB_METADATA_VERSION_PATCH: str = "amazon.opentelemetry.distro._utils.version"
@@ -160,6 +196,14 @@ def _test_unpatched_botocore_instrumentation(self):
160196
"bedrock-agent-runtime" in _KNOWN_EXTENSIONS, "Upstream has added a Bedrock Agent Runtime extension"
161197
)
162198

199+
# Bedrock AgentCore
200+
self.assertFalse("bedrock-agentcore" in _KNOWN_EXTENSIONS, "Upstream has added a Bedrock AgentCore extension")
201+
202+
# Bedrock AgentCore Control
203+
self.assertFalse(
204+
"bedrock-agentcore-control" in _KNOWN_EXTENSIONS, "Upstream has added a Bedrock AgentCore Control extension"
205+
)
206+
163207
# BedrockRuntime
164208
self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS, "Upstream has added a bedrock-runtime extension")
165209

@@ -237,6 +281,41 @@ def _test_patched_botocore_instrumentation(self):
237281
bedrock_agent_runtime_sucess_attributes: Dict[str, str] = _do_on_success_bedrock("bedrock-agent-runtime")
238282
self.assertEqual(len(bedrock_agent_runtime_sucess_attributes), 0)
239283

284+
# Bedrock AgentCore
285+
self.assertTrue("bedrock-agentcore" in _KNOWN_EXTENSIONS)
286+
bedrock_agentcore_attributes: Dict[str, str] = _do_extract_bedrock_agentcore_attributes()
287+
# Runtime attributes
288+
self.assertEqual(bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_RUNTIME_ARN], _AGENTCORE_RUNTIME_ARN)
289+
self.assertEqual(
290+
bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN], _AGENTCORE_RUNTIME_ENDPOINT_ARN
291+
)
292+
self.assertEqual(bedrock_agentcore_attributes[GEN_AI_RUNTIME_ID], _AGENTCORE_RUNTIME_ID)
293+
# Browser attributes
294+
self.assertEqual(bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_BROWSER_ARN], _AGENTCORE_BROWSER_ARN)
295+
self.assertEqual(bedrock_agentcore_attributes[GEN_AI_BROWSER_ID], _AGENTCORE_BROWSER_ID)
296+
# Code interpreter attributes
297+
self.assertEqual(
298+
bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN], _AGENTCORE_CODE_INTERPRETER_ARN
299+
)
300+
self.assertEqual(bedrock_agentcore_attributes[GEN_AI_CODE_INTERPRETER_ID], _AGENTCORE_CODE_INTERPRETER_ID)
301+
# Gateway attributes
302+
self.assertEqual(bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_GATEWAY_ARN], _AGENTCORE_GATEWAY_ARN)
303+
self.assertEqual(bedrock_agentcore_attributes[GEN_AI_GATEWAY_ID], _AGENTCORE_GATEWAY_ID)
304+
self.assertEqual(bedrock_agentcore_attributes[AWS_GATEWAY_TARGET_ID], _AGENTCORE_TARGET_ID)
305+
# Memory attributes
306+
self.assertEqual(bedrock_agentcore_attributes[GEN_AI_MEMORY_ID], _AGENTCORE_MEMORY_ID)
307+
self.assertEqual(bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_MEMORY_ARN], _AGENTCORE_MEMORY_ARN)
308+
# Auth and identity attributes
309+
self.assertEqual(
310+
bedrock_agentcore_attributes[AWS_AUTH_CREDENTIAL_PROVIDER_ARN], _AGENTCORE_CREDENTIAL_PROVIDER_ARN
311+
)
312+
self.assertEqual(
313+
bedrock_agentcore_attributes[AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN], _AGENTCORE_WORKLOAD_IDENTITY_ARN
314+
)
315+
316+
# Bedrock AgentCore Control
317+
self.assertTrue("bedrock-agentcore-control" in _KNOWN_EXTENSIONS)
318+
240319
# BedrockRuntime
241320
self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS)
242321

@@ -854,6 +933,31 @@ def _do_extract_lambda_attributes() -> Dict[str, str]:
854933
return _do_extract_attributes(service_name, params)
855934

856935

936+
def _do_extract_bedrock_agentcore_attributes() -> Dict[str, str]:
937+
service_name: str = "bedrock-agentcore"
938+
params: Dict[str, Any] = {
939+
"agentRuntimeArn": _AGENTCORE_RUNTIME_ARN,
940+
"agentRuntimeEndpointArn": _AGENTCORE_RUNTIME_ENDPOINT_ARN,
941+
"agentRuntimeId": _AGENTCORE_RUNTIME_ID,
942+
"browserArn": _AGENTCORE_BROWSER_ARN,
943+
"browserId": _AGENTCORE_BROWSER_ID,
944+
"browserIdentifier": _AGENTCORE_BROWSER_ID,
945+
"codeInterpreterArn": _AGENTCORE_CODE_INTERPRETER_ARN,
946+
"codeInterpreterId": _AGENTCORE_CODE_INTERPRETER_ID,
947+
"codeInterpreterIdentifier": _AGENTCORE_CODE_INTERPRETER_ID,
948+
"gatewayArn": _AGENTCORE_GATEWAY_ARN,
949+
"gatewayId": _AGENTCORE_GATEWAY_ID,
950+
"gatewayIdentifier": _AGENTCORE_GATEWAY_ID,
951+
"targetId": _AGENTCORE_TARGET_ID,
952+
"memoryId": _AGENTCORE_MEMORY_ID,
953+
"credentialProviderArn": _AGENTCORE_CREDENTIAL_PROVIDER_ARN,
954+
"workloadIdentityArn": _AGENTCORE_WORKLOAD_IDENTITY_ARN,
955+
"memory": {"arn": _AGENTCORE_MEMORY_ARN, "id": _AGENTCORE_MEMORY_ID},
956+
"workloadIdentityDetails": {"workloadIdentityArn": _AGENTCORE_WORKLOAD_IDENTITY_ARN},
957+
}
958+
return _do_extract_attributes(service_name, params)
959+
960+
857961
def _do_extract_attributes(service_name: str, params: Dict[str, Any], operation: str = None) -> Dict[str, str]:
858962
mock_call_context: MagicMock = MagicMock()
859963
mock_call_context.params = params

0 commit comments

Comments
 (0)