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
3144 _BotocoreInstrumentorContext ,
3245 _BotoResultT ,
3346)
47+ from opentelemetry .instrumentation .utils import (
48+ is_instrumentation_enabled ,
49+ suppress_http_instrumentation ,
50+ )
3451from opentelemetry .semconv .trace import SpanAttributes
3552from opentelemetry .trace .span import Span
3653
@@ -40,6 +57,7 @@ def _apply_botocore_instrumentation_patches() -> None:
4057
4158 Adds patches to provide additional support and Java parity for Kinesis, S3, and SQS.
4259 """
60+ _apply_botocore_api_call_patch ()
4361 _apply_botocore_kinesis_patch ()
4462 _apply_botocore_s3_patch ()
4563 _apply_botocore_sqs_patch ()
@@ -48,6 +66,7 @@ def _apply_botocore_instrumentation_patches() -> None:
4866 _apply_botocore_sns_patch ()
4967 _apply_botocore_stepfunctions_patch ()
5068 _apply_botocore_lambda_patch ()
69+ _apply_botocore_dynamodb_patch ()
5170
5271
5372def _apply_botocore_lambda_patch () -> None :
@@ -209,6 +228,85 @@ def _apply_botocore_bedrock_patch() -> None:
209228 _KNOWN_EXTENSIONS ["bedrock-runtime" ] = _lazy_load ("." , "_BedrockRuntimeExtension" )
210229
211230
231+ def _apply_botocore_dynamodb_patch () -> None :
232+ """Botocore instrumentation patch for DynamoDB
233+
234+ This patch adds an extension to the upstream's list of known extensions for DynamoDB.
235+ Extensions allow for custom logic for adding service-specific information to
236+ spans, such as attributes. Specifically, we are adding logic to add the
237+ `aws.table.arn` attribute, to be used to generate RemoteTarget and achieve
238+ parity with the Java instrumentation.
239+ """
240+ old_on_success = _DynamoDbExtension .on_success
241+
242+ def patch_on_success (self , span : Span , result : _BotoResultT ):
243+ old_on_success (self , span , result )
244+ table = result .get ("Table" , {})
245+ table_arn = table .get ("TableArn" )
246+ if table_arn :
247+ span .set_attribute (AWS_DYNAMODB_TABLE_ARN , table_arn )
248+
249+ _DynamoDbExtension .on_success = patch_on_success
250+
251+
252+ def _apply_botocore_api_call_patch () -> None :
253+ def patched_api_call (self , original_func , instance , args , kwargs ):
254+ if not is_instrumentation_enabled ():
255+ return original_func (* args , ** kwargs )
256+
257+ call_context = _determine_call_context (instance , args )
258+ if call_context is None :
259+ return original_func (* args , ** kwargs )
260+
261+ extension = _find_extension (call_context )
262+ if not extension .should_trace_service_call ():
263+ return original_func (* args , ** kwargs )
264+
265+ attributes = {
266+ SpanAttributes .RPC_SYSTEM : "aws-api" ,
267+ SpanAttributes .RPC_SERVICE : call_context .service_id ,
268+ SpanAttributes .RPC_METHOD : call_context .operation ,
269+ "aws.region" : call_context .region ,
270+ AWS_AUTH_REGION : call_context .region ,
271+ }
272+ credentials = instance ._get_credentials ()
273+
274+ if credentials is not None :
275+ access_key = credentials .access_key
276+ if access_key is not None :
277+ attributes [AWS_AUTH_ACCESS_KEY ] = access_key
278+
279+ _safe_invoke (extension .extract_attributes , attributes )
280+
281+ with self ._tracer .start_as_current_span (
282+ call_context .span_name ,
283+ kind = call_context .span_kind ,
284+ attributes = attributes ,
285+ ) as span :
286+ _safe_invoke (extension .before_service_call , span )
287+ self ._call_request_hook (span , call_context )
288+
289+ try :
290+ with suppress_http_instrumentation ():
291+ result = None
292+ try :
293+ result = original_func (* args , ** kwargs )
294+ except ClientError as error :
295+ result = getattr (error , "response" , None )
296+ _apply_response_attributes (span , result )
297+ _safe_invoke (extension .on_error , span , error )
298+ raise
299+ _apply_response_attributes (span , result )
300+ _safe_invoke (extension .on_success , span , result )
301+ finally :
302+ _safe_invoke (extension .after_service_call )
303+ self ._call_response_hook (span , call_context , result )
304+
305+ return result
306+
307+ BotocoreInstrumentor ._patched_api_call = patched_api_call
308+
309+
212310# The OpenTelemetry Authors code
213311def _lazy_load (module , cls ):
214312 """Clone of upstream opentelemetry.instrumentation.botocore.extensions.lazy_load
@@ -266,3 +364,6 @@ def extract_attributes(self, attributes: _AttributeMapT):
266364 stream_name = self ._call_context .params .get ("StreamName" )
267365 if stream_name :
268366 attributes [AWS_KINESIS_STREAM_NAME ] = stream_name
367+ stream_arn = self ._call_context .params .get ("StreamARN" )
368+ if stream_arn :
369+ attributes [AWS_KINESIS_STREAM_ARN ] = stream_arn
0 commit comments