5050
5151import functools
5252import types
53+ from time import time
5354from typing import Collection
55+ from urllib .parse import urlparse
5456
5557from requests .models import Response
5658from requests .sessions import Session
6466 _SUPPRESS_INSTRUMENTATION_KEY ,
6567 http_status_to_status_code ,
6668)
69+ from opentelemetry .metrics import get_meter
6770from opentelemetry .propagate import inject
6871from opentelemetry .semconv .trace import SpanAttributes
6972from opentelemetry .trace import SpanKind , get_tracer
8790# pylint: disable=unused-argument
8891# pylint: disable=R0915
8992def _instrument (
90- tracer , span_callback = None , name_callback = None , excluded_urls = None
93+ tracer ,
94+ span_callback = None ,
95+ name_callback = None ,
96+ excluded_urls = None ,
97+ metric_recorder = None ,
9198):
9299 """Enables tracing of all requests calls that go through
93100 :code:`requests.session.Session.request` (this includes
@@ -166,6 +173,7 @@ def _instrumented_requests_call(
166173 SpanAttributes .HTTP_METHOD : method ,
167174 SpanAttributes .HTTP_URL : url ,
168175 }
176+ parsed_url = urlparse (url )
169177
170178 with tracer .start_as_current_span (
171179 span_name , kind = SpanKind .CLIENT , attributes = span_attributes
@@ -178,6 +186,8 @@ def _instrumented_requests_call(
178186 token = context .attach (
179187 context .set_value (_SUPPRESS_HTTP_INSTRUMENTATION_KEY , True )
180188 )
189+ start_time = time ()
190+ metric_labels = {}
181191 try :
182192 result = call_wrapped () # *** PROCEED
183193 except Exception as exc : # pylint: disable=W0703
@@ -194,9 +204,30 @@ def _instrumented_requests_call(
194204 span .set_status (
195205 Status (http_status_to_status_code (result .status_code ))
196206 )
207+
208+ metric_labels ["http.status_code" ] = result .status_code
209+ metric_labels ["http.flavor" ] = (
210+ "1.1" if result .raw .version == 11 else "1.0"
211+ )
212+
197213 if span_callback is not None :
198214 span_callback (span , result )
199215
216+ elapsed_time = time () - start_time
217+
218+ metric_labels .update (
219+ {
220+ "http.method" : method ,
221+ "http.host" : parsed_url .hostname ,
222+ "http.scheme" : parsed_url .scheme ,
223+ "http.url" : url ,
224+ }
225+ )
226+
227+ metric_recorder .record (
228+ elapsed_time * 1000 , attributes = metric_labels
229+ )
230+
200231 if exception is not None :
201232 raise exception .with_traceback (exception .__traceback__ )
202233
@@ -261,13 +292,25 @@ def _instrument(self, **kwargs):
261292 tracer_provider = kwargs .get ("tracer_provider" )
262293 tracer = get_tracer (__name__ , __version__ , tracer_provider )
263294 excluded_urls = kwargs .get ("excluded_urls" )
295+ meter_provider = kwargs .get ("meter_provider" )
296+ self ._meter = get_meter (
297+ __name__ ,
298+ __version__ ,
299+ meter_provider ,
300+ )
301+ self ._metric_recorder = self ._meter .create_histogram (
302+ name = "http.client.duration" ,
303+ unit = "ms" ,
304+ description = "measures the duration of the outbound HTTP request" ,
305+ )
264306 _instrument (
265307 tracer ,
266308 span_callback = kwargs .get ("span_callback" ),
267309 name_callback = kwargs .get ("name_callback" ),
268310 excluded_urls = _excluded_urls_from_env
269311 if excluded_urls is None
270312 else parse_excluded_urls (excluded_urls ),
313+ metric_recorder = self ._metric_recorder ,
271314 )
272315
273316 def _uninstrument (self , ** kwargs ):
0 commit comments