4444
4545API
4646---
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()
4781"""
4882
4983import json
@@ -91,16 +125,29 @@ class BotocoreInstrumentor(BaseInstrumentor):
91125 See `BaseInstrumentor`
92126 """
93127
128+ def __init__ (self ):
129+ super ().__init__ ()
130+ self .request_hooks = dict ()
131+ self .response_hooks = dict ()
132+
94133 def instrumentation_dependencies (self ) -> Collection [str ]:
95134 return _instruments
96135
97136 def _instrument (self , ** kwargs ):
98-
99137 # pylint: disable=attribute-defined-outside-init
100138 self ._tracer = get_tracer (
101139 __name__ , __version__ , kwargs .get ("tracer_provider" )
102140 )
103141
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+
104151 wrap_function_wrapper (
105152 "botocore.client" ,
106153 "BaseClient._make_api_call" ,
@@ -159,21 +206,18 @@ def _patched_api_call(self, original_func, instance, args, kwargs):
159206 ):
160207 BotocoreInstrumentor ._patch_lambda_invoke (api_params )
161208
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+ )
172212
173213 token = context_api .attach (
174214 context_api .set_value (_SUPPRESS_HTTP_INSTRUMENTATION_KEY , True )
175215 )
176216
217+ self .apply_request_hook (
218+ span , service_name , operation_name , api_params
219+ )
220+
177221 try :
178222 result = original_func (* args , ** kwargs )
179223 except ClientError as ex :
@@ -184,38 +228,73 @@ def _patched_api_call(self, original_func, instance, args, kwargs):
184228 if error :
185229 result = error .response
186230
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 )
217236
218237 if error :
219238 raise error
220239
221240 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