44
44
45
45
API
46
46
---
47
+
48
+ The `instrument` method accepts the following keyword args:
49
+
50
+ tracer_provider (TracerProvider) - an optional tracer provider
51
+ request_hooks (dict) - a mapping between service names their respective callable request hooks
52
+ * a request hook signature is: def request_hook(span: Span, operation_name: str, api_params: dict) -> None
53
+ response_hooks (dict) - a mapping between service names their respective callable response hooks
54
+ * a response hook signature is: def response_hook(span: Span, operation_name: str, result: dict) -> None
55
+
56
+ for example:
57
+
58
+ .. code: python
59
+
60
+ from opentelemetry.instrumentation.botocore import BotocoreInstrumentor
61
+ import botocore
62
+
63
+ def ec2_request_hook(span, operation_name, api_params):
64
+ # request hook logic
65
+
66
+ def ec2_response_hook(span, operation_name, result):
67
+ # response hook logic
68
+
69
+ # Instrument Botocore with hooks
70
+ BotocoreInstrumentor().instrument(
71
+ request_hooks={"ec2": ec2_request_hook}, response_hooks={"ec2": ec2_response_hook}
72
+ )
73
+
74
+ # This will create a span with Botocore-specific attributes, including custom attributes added from the hooks
75
+ session = botocore.session.get_session()
76
+ session.set_credentials(
77
+ access_key="access-key", secret_key="secret-key"
78
+ )
79
+ ec2 = self.session.create_client("ec2", region_name="us-west-2")
80
+ ec2.describe_instances()
47
81
"""
48
82
49
83
import json
@@ -91,16 +125,29 @@ class BotocoreInstrumentor(BaseInstrumentor):
91
125
See `BaseInstrumentor`
92
126
"""
93
127
128
+ def __init__ (self ):
129
+ super ().__init__ ()
130
+ self .request_hooks = dict ()
131
+ self .response_hooks = dict ()
132
+
94
133
def instrumentation_dependencies (self ) -> Collection [str ]:
95
134
return _instruments
96
135
97
136
def _instrument (self , ** kwargs ):
98
-
99
137
# pylint: disable=attribute-defined-outside-init
100
138
self ._tracer = get_tracer (
101
139
__name__ , __version__ , kwargs .get ("tracer_provider" )
102
140
)
103
141
142
+ request_hooks = kwargs .get ("request_hooks" )
143
+ response_hooks = kwargs .get ("response_hooks" )
144
+
145
+ if isinstance (request_hooks , dict ):
146
+ self .request_hooks = request_hooks
147
+
148
+ if isinstance (response_hooks , dict ):
149
+ self .response_hooks = response_hooks
150
+
104
151
wrap_function_wrapper (
105
152
"botocore.client" ,
106
153
"BaseClient._make_api_call" ,
@@ -159,21 +206,18 @@ def _patched_api_call(self, original_func, instance, args, kwargs):
159
206
):
160
207
BotocoreInstrumentor ._patch_lambda_invoke (api_params )
161
208
162
- if span .is_recording ():
163
- span .set_attribute ("aws.operation" , operation_name )
164
- span .set_attribute ("aws.region" , instance .meta .region_name )
165
- span .set_attribute ("aws.service" , service_name )
166
- if "QueueUrl" in api_params :
167
- span .set_attribute ("aws.queue_url" , api_params ["QueueUrl" ])
168
- if "TableName" in api_params :
169
- span .set_attribute (
170
- "aws.table_name" , api_params ["TableName" ]
171
- )
209
+ self ._set_api_call_attributes (
210
+ span , instance , service_name , operation_name , api_params
211
+ )
172
212
173
213
token = context_api .attach (
174
214
context_api .set_value (_SUPPRESS_HTTP_INSTRUMENTATION_KEY , True )
175
215
)
176
216
217
+ self .apply_request_hook (
218
+ span , service_name , operation_name , api_params
219
+ )
220
+
177
221
try :
178
222
result = original_func (* args , ** kwargs )
179
223
except ClientError as ex :
@@ -184,38 +228,73 @@ def _patched_api_call(self, original_func, instance, args, kwargs):
184
228
if error :
185
229
result = error .response
186
230
187
- if span .is_recording ():
188
- if "ResponseMetadata" in result :
189
- metadata = result ["ResponseMetadata" ]
190
- req_id = None
191
- if "RequestId" in metadata :
192
- req_id = metadata ["RequestId" ]
193
- elif "HTTPHeaders" in metadata :
194
- headers = metadata ["HTTPHeaders" ]
195
- if "x-amzn-RequestId" in headers :
196
- req_id = headers ["x-amzn-RequestId" ]
197
- elif "x-amz-request-id" in headers :
198
- req_id = headers ["x-amz-request-id" ]
199
- elif "x-amz-id-2" in headers :
200
- req_id = headers ["x-amz-id-2" ]
201
-
202
- if req_id :
203
- span .set_attribute (
204
- "aws.request_id" , req_id ,
205
- )
206
-
207
- if "RetryAttempts" in metadata :
208
- span .set_attribute (
209
- "retry_attempts" , metadata ["RetryAttempts" ],
210
- )
211
-
212
- if "HTTPStatusCode" in metadata :
213
- span .set_attribute (
214
- SpanAttributes .HTTP_STATUS_CODE ,
215
- metadata ["HTTPStatusCode" ],
216
- )
231
+ self .apply_response_hook (
232
+ span , service_name , operation_name , result
233
+ )
234
+
235
+ self ._set_api_call_result_attributes (span , result )
217
236
218
237
if error :
219
238
raise error
220
239
221
240
return result
241
+
242
+ @staticmethod
243
+ def _set_api_call_attributes (
244
+ span , instance , service_name , operation_name , api_params
245
+ ):
246
+ if span .is_recording ():
247
+ span .set_attribute ("aws.operation" , operation_name )
248
+ span .set_attribute ("aws.region" , instance .meta .region_name )
249
+ span .set_attribute ("aws.service" , service_name )
250
+ if "QueueUrl" in api_params :
251
+ span .set_attribute ("aws.queue_url" , api_params ["QueueUrl" ])
252
+ if "TableName" in api_params :
253
+ span .set_attribute ("aws.table_name" , api_params ["TableName" ])
254
+
255
+ @staticmethod
256
+ def _set_api_call_result_attributes (span , result ):
257
+ if span .is_recording ():
258
+ if "ResponseMetadata" in result :
259
+ metadata = result ["ResponseMetadata" ]
260
+ req_id = None
261
+ if "RequestId" in metadata :
262
+ req_id = metadata ["RequestId" ]
263
+ elif "HTTPHeaders" in metadata :
264
+ headers = metadata ["HTTPHeaders" ]
265
+ if "x-amzn-RequestId" in headers :
266
+ req_id = headers ["x-amzn-RequestId" ]
267
+ elif "x-amz-request-id" in headers :
268
+ req_id = headers ["x-amz-request-id" ]
269
+ elif "x-amz-id-2" in headers :
270
+ req_id = headers ["x-amz-id-2" ]
271
+
272
+ if req_id :
273
+ span .set_attribute (
274
+ "aws.request_id" , req_id ,
275
+ )
276
+
277
+ if "RetryAttempts" in metadata :
278
+ span .set_attribute (
279
+ "retry_attempts" , metadata ["RetryAttempts" ],
280
+ )
281
+
282
+ if "HTTPStatusCode" in metadata :
283
+ span .set_attribute (
284
+ SpanAttributes .HTTP_STATUS_CODE ,
285
+ metadata ["HTTPStatusCode" ],
286
+ )
287
+
288
+ def apply_request_hook (
289
+ self , span , service_name , operation_name , api_params
290
+ ):
291
+ if service_name in self .request_hooks :
292
+ request_hook = self .request_hooks .get (service_name )
293
+ if callable (request_hook ):
294
+ request_hook (span , operation_name , api_params )
295
+
296
+ def apply_response_hook (self , span , service_name , operation_name , result ):
297
+ if service_name in self .response_hooks :
298
+ response_hook = self .response_hooks .get (service_name )
299
+ if callable (response_hook ):
300
+ response_hook (span , operation_name , result )
0 commit comments