50
50
51
51
import functools
52
52
import types
53
- from typing import Collection
53
+ from timeit import default_timer
54
+ from typing import Callable , Collection , Iterable , Optional
55
+ from urllib .parse import urlparse
54
56
55
57
from requests .models import Response
56
58
from requests .sessions import Session
67
69
_SUPPRESS_INSTRUMENTATION_KEY ,
68
70
http_status_to_status_code ,
69
71
)
72
+ from opentelemetry .metrics import Histogram , get_meter
70
73
from opentelemetry .propagate import inject
71
74
from opentelemetry .semconv .trace import SpanAttributes
72
- from opentelemetry .trace import SpanKind , get_tracer
75
+ from opentelemetry .trace import SpanKind , Tracer , get_tracer
76
+ from opentelemetry .trace .span import Span
73
77
from opentelemetry .trace .status import Status
74
78
from opentelemetry .util .http import (
75
79
get_excluded_urls ,
84
88
# pylint: disable=unused-argument
85
89
# pylint: disable=R0915
86
90
def _instrument (
87
- tracer , span_callback = None , name_callback = None , excluded_urls = None
91
+ tracer : Tracer ,
92
+ duration_histogram : Histogram ,
93
+ span_callback : Optional [Callable [[Span , Response ], str ]] = None ,
94
+ name_callback : Optional [Callable [[str , str ], str ]] = None ,
95
+ excluded_urls : Iterable [str ] = None ,
88
96
):
89
97
"""Enables tracing of all requests calls that go through
90
98
:code:`requests.session.Session.request` (this includes
@@ -140,6 +148,7 @@ def call_wrapped():
140
148
request .method , request .url , call_wrapped , get_or_create_headers
141
149
)
142
150
151
+ # pylint: disable-msg=too-many-locals,too-many-branches
143
152
def _instrumented_requests_call (
144
153
method : str , url : str , call_wrapped , get_or_create_headers
145
154
):
@@ -164,6 +173,23 @@ def _instrumented_requests_call(
164
173
SpanAttributes .HTTP_URL : url ,
165
174
}
166
175
176
+ metric_labels = {
177
+ SpanAttributes .HTTP_METHOD : method ,
178
+ }
179
+
180
+ try :
181
+ parsed_url = urlparse (url )
182
+ metric_labels [SpanAttributes .HTTP_SCHEME ] = parsed_url .scheme
183
+ if parsed_url .hostname :
184
+ metric_labels [SpanAttributes .HTTP_HOST ] = parsed_url .hostname
185
+ metric_labels [
186
+ SpanAttributes .NET_PEER_NAME
187
+ ] = parsed_url .hostname
188
+ if parsed_url .port :
189
+ metric_labels [SpanAttributes .NET_PEER_PORT ] = parsed_url .port
190
+ except ValueError :
191
+ pass
192
+
167
193
with tracer .start_as_current_span (
168
194
span_name , kind = SpanKind .CLIENT , attributes = span_attributes
169
195
) as span , set_ip_on_next_http_connection (span ):
@@ -175,12 +201,18 @@ def _instrumented_requests_call(
175
201
token = context .attach (
176
202
context .set_value (_SUPPRESS_HTTP_INSTRUMENTATION_KEY , True )
177
203
)
204
+
205
+ start_time = default_timer ()
206
+
178
207
try :
179
208
result = call_wrapped () # *** PROCEED
180
209
except Exception as exc : # pylint: disable=W0703
181
210
exception = exc
182
211
result = getattr (exc , "response" , None )
183
212
finally :
213
+ elapsed_time = max (
214
+ round ((default_timer () - start_time ) * 1000 ), 0
215
+ )
184
216
context .detach (token )
185
217
186
218
if isinstance (result , Response ):
@@ -191,9 +223,23 @@ def _instrumented_requests_call(
191
223
span .set_status (
192
224
Status (http_status_to_status_code (result .status_code ))
193
225
)
226
+
227
+ metric_labels [
228
+ SpanAttributes .HTTP_STATUS_CODE
229
+ ] = result .status_code
230
+
231
+ if result .raw is not None :
232
+ version = getattr (result .raw , "version" , None )
233
+ if version :
234
+ metric_labels [SpanAttributes .HTTP_FLAVOR ] = (
235
+ "1.1" if version == 11 else "1.0"
236
+ )
237
+
194
238
if span_callback is not None :
195
239
span_callback (span , result )
196
240
241
+ duration_histogram .record (elapsed_time , attributes = metric_labels )
242
+
197
243
if exception is not None :
198
244
raise exception .with_traceback (exception .__traceback__ )
199
245
@@ -258,8 +304,20 @@ def _instrument(self, **kwargs):
258
304
tracer_provider = kwargs .get ("tracer_provider" )
259
305
tracer = get_tracer (__name__ , __version__ , tracer_provider )
260
306
excluded_urls = kwargs .get ("excluded_urls" )
307
+ meter_provider = kwargs .get ("meter_provider" )
308
+ meter = get_meter (
309
+ __name__ ,
310
+ __version__ ,
311
+ meter_provider ,
312
+ )
313
+ duration_histogram = meter .create_histogram (
314
+ name = "http.client.duration" ,
315
+ unit = "ms" ,
316
+ description = "measures the duration of the outbound HTTP request" ,
317
+ )
261
318
_instrument (
262
319
tracer ,
320
+ duration_histogram ,
263
321
span_callback = kwargs .get ("span_callback" ),
264
322
name_callback = kwargs .get ("name_callback" ),
265
323
excluded_urls = _excluded_urls_from_env
0 commit comments