3
3
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
4
4
import importlib
5
5
6
+ from botocore .exceptions import ClientError
7
+
6
8
from 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 ,
7
13
AWS_KINESIS_STREAM_NAME ,
8
14
AWS_LAMBDA_FUNCTION_ARN ,
9
15
AWS_LAMBDA_FUNCTION_NAME ,
20
26
_BedrockAgentRuntimeExtension ,
21
27
_BedrockExtension ,
22
28
)
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
24
37
from opentelemetry .instrumentation .botocore .extensions .lmbd import _LambdaExtension
25
38
from opentelemetry .instrumentation .botocore .extensions .sns import _SnsExtension
26
39
from opentelemetry .instrumentation .botocore .extensions .sqs import _SqsExtension
30
43
_BotocoreInstrumentorContext ,
31
44
_BotoResultT ,
32
45
)
46
+ from opentelemetry .instrumentation .utils import (
47
+ is_instrumentation_enabled ,
48
+ suppress_http_instrumentation ,
49
+ )
33
50
from opentelemetry .semconv .trace import SpanAttributes
34
51
from opentelemetry .trace .span import Span
35
52
@@ -39,6 +56,7 @@ def _apply_botocore_instrumentation_patches() -> None:
39
56
40
57
Adds patches to provide additional support and Java parity for Kinesis, S3, and SQS.
41
58
"""
59
+ _apply_botocore_api_call_patch ()
42
60
_apply_botocore_kinesis_patch ()
43
61
_apply_botocore_s3_patch ()
44
62
_apply_botocore_sqs_patch ()
@@ -47,6 +65,7 @@ def _apply_botocore_instrumentation_patches() -> None:
47
65
_apply_botocore_sns_patch ()
48
66
_apply_botocore_stepfunctions_patch ()
49
67
_apply_botocore_lambda_patch ()
68
+ _apply_botocore_dynamodb_patch ()
50
69
51
70
52
71
def _apply_botocore_lambda_patch () -> None :
@@ -208,6 +227,85 @@ def _apply_botocore_bedrock_patch() -> None:
208
227
# bedrock-runtime is handled by upstream
209
228
210
229
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
+
211
309
# The OpenTelemetry Authors code
212
310
def _lazy_load (module , cls ):
213
311
"""Clone of upstream opentelemetry.instrumentation.botocore.extensions.lazy_load
@@ -265,3 +363,6 @@ def extract_attributes(self, attributes: _AttributeMapT):
265
363
stream_name = self ._call_context .params .get ("StreamName" )
266
364
if stream_name :
267
365
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