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