33# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
44import importlib
55
6+ from botocore .exceptions import ClientError
7+
68from amazon .opentelemetry .distro ._aws_attribute_keys import (
9+ AWS_AUTH_ACCESS_KEY ,
10+ AWS_AUTH_REGION ,
11+ AWS_DYNAMODB_TABLE_ARN ,
12+ AWS_KINESIS_STREAM_ARN ,
713 AWS_KINESIS_STREAM_NAME ,
814 AWS_LAMBDA_FUNCTION_ARN ,
915 AWS_LAMBDA_FUNCTION_NAME ,
2127 _BedrockExtension ,
2228 _BedrockRuntimeExtension ,
2329)
24- from opentelemetry .instrumentation .botocore .extensions import _KNOWN_EXTENSIONS
30+ from opentelemetry .instrumentation .botocore import (
31+ BotocoreInstrumentor ,
32+ _apply_response_attributes ,
33+ _determine_call_context ,
34+ _safe_invoke ,
35+ )
36+ from opentelemetry .instrumentation .botocore .extensions import _KNOWN_EXTENSIONS , _find_extension
37+ from opentelemetry .instrumentation .botocore .extensions .dynamodb import _DynamoDbExtension
2538from opentelemetry .instrumentation .botocore .extensions .lmbd import _LambdaExtension
2639from opentelemetry .instrumentation .botocore .extensions .sns import _SnsExtension
2740from opentelemetry .instrumentation .botocore .extensions .sqs import _SqsExtension
2841from opentelemetry .instrumentation .botocore .extensions .types import _AttributeMapT , _AwsSdkExtension , _BotoResultT
42+ from opentelemetry .instrumentation .utils import (
43+ is_instrumentation_enabled ,
44+ suppress_http_instrumentation ,
45+ )
2946from opentelemetry .semconv .trace import SpanAttributes
3047from opentelemetry .trace .span import Span
3148
@@ -35,6 +52,7 @@ def _apply_botocore_instrumentation_patches() -> None:
3552
3653 Adds patches to provide additional support and Java parity for Kinesis, S3, and SQS.
3754 """
55+ _apply_botocore_api_call_patch ()
3856 _apply_botocore_kinesis_patch ()
3957 _apply_botocore_s3_patch ()
4058 _apply_botocore_sqs_patch ()
@@ -43,6 +61,7 @@ def _apply_botocore_instrumentation_patches() -> None:
4361 _apply_botocore_sns_patch ()
4462 _apply_botocore_stepfunctions_patch ()
4563 _apply_botocore_lambda_patch ()
64+ _apply_botocore_dynamodb_patch ()
4665
4766
4867def _apply_botocore_lambda_patch () -> None :
@@ -204,6 +223,85 @@ def _apply_botocore_bedrock_patch() -> None:
204223 _KNOWN_EXTENSIONS ["bedrock-runtime" ] = _lazy_load ("." , "_BedrockRuntimeExtension" )
205224
206225
226+ def _apply_botocore_dynamodb_patch () -> None :
227+ """Botocore instrumentation patch for DynamoDB
228+
229+ This patch adds an extension to the upstream's list of known extensions for DynamoDB.
230+ Extensions allow for custom logic for adding service-specific information to
231+ spans, such as attributes. Specifically, we are adding logic to add the
232+ `aws.table.arn` attribute, to be used to generate RemoteTarget and achieve
233+ parity with the Java instrumentation.
234+ """
235+ old_on_success = _DynamoDbExtension .on_success
236+
237+ def patch_on_success (self , span : Span , result : _BotoResultT ):
238+ old_on_success (self , span , result )
239+ table = result .get ("Table" , {})
240+ table_arn = table .get ("TableArn" )
241+ if table_arn :
242+ span .set_attribute (AWS_DYNAMODB_TABLE_ARN , table_arn )
243+
244+ _DynamoDbExtension .on_success = patch_on_success
245+
246+
247+ def _apply_botocore_api_call_patch () -> None :
248+ def patched_api_call (self , original_func , instance , args , kwargs ):
249+ if not is_instrumentation_enabled ():
250+ return original_func (* args , ** kwargs )
251+
252+ call_context = _determine_call_context (instance , args )
253+ if call_context is None :
254+ return original_func (* args , ** kwargs )
255+
256+ extension = _find_extension (call_context )
257+ if not extension .should_trace_service_call ():
258+ return original_func (* args , ** kwargs )
259+
260+ attributes = {
261+ SpanAttributes .RPC_SYSTEM : "aws-api" ,
262+ SpanAttributes .RPC_SERVICE : call_context .service_id ,
263+ SpanAttributes .RPC_METHOD : call_context .operation ,
264+ "aws.region" : call_context .region ,
265+ AWS_AUTH_REGION : call_context .region ,
266+ }
267+ credentials = instance ._get_credentials ()
268+
269+ if credentials is not None :
270+ access_key = credentials .access_key
271+ if access_key is not None :
272+ attributes [AWS_AUTH_ACCESS_KEY ] = access_key
273+
274+ _safe_invoke (extension .extract_attributes , attributes )
275+
276+ with self ._tracer .start_as_current_span (
277+ call_context .span_name ,
278+ kind = call_context .span_kind ,
279+ attributes = attributes ,
280+ ) as span :
281+ _safe_invoke (extension .before_service_call , span )
282+ self ._call_request_hook (span , call_context )
283+
284+ try :
285+ with suppress_http_instrumentation ():
286+ result = None
287+ try :
288+ result = original_func (* args , ** kwargs )
289+ except ClientError as error :
290+ result = getattr (error , "response" , None )
291+ _apply_response_attributes (span , result )
292+ _safe_invoke (extension .on_error , span , error )
293+ raise
294+ _apply_response_attributes (span , result )
295+ _safe_invoke (extension .on_success , span , result )
296+ finally :
297+ _safe_invoke (extension .after_service_call )
298+ self ._call_response_hook (span , call_context , result )
299+
300+ return result
301+
302+ BotocoreInstrumentor ._patched_api_call = patched_api_call
303+
304+
207305# The OpenTelemetry Authors code
208306def _lazy_load (module , cls ):
209307 """Clone of upstream opentelemetry.instrumentation.botocore.extensions.lazy_load
@@ -261,3 +359,6 @@ def extract_attributes(self, attributes: _AttributeMapT):
261359 stream_name = self ._call_context .params .get ("StreamName" )
262360 if stream_name :
263361 attributes [AWS_KINESIS_STREAM_NAME ] = stream_name
362+ stream_arn = self ._call_context .params .get ("StreamARN" )
363+ if stream_arn :
364+ attributes [AWS_KINESIS_STREAM_ARN ] = stream_arn
0 commit comments