@@ -85,25 +85,49 @@ def response_hook(span, request_obj, response)
8585 Request ,
8686)
8787
88+ from opentelemetry .instrumentation ._semconv import (
89+ _client_duration_attrs_new ,
90+ _client_duration_attrs_old ,
91+ _filter_semconv_duration_attrs ,
92+ _get_schema_url ,
93+ _HTTPStabilityMode ,
94+ _OpenTelemetrySemanticConventionStability ,
95+ _OpenTelemetryStabilitySignalType ,
96+ _report_new ,
97+ _report_old ,
98+ _set_http_method ,
99+ _set_http_network_protocol_version ,
100+ _set_http_url ,
101+ _set_status ,
102+ )
88103from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
89104from opentelemetry .instrumentation .urllib .package import _instruments
90105from opentelemetry .instrumentation .urllib .version import __version__
91106from opentelemetry .instrumentation .utils import (
92- http_status_to_status_code ,
93107 is_http_instrumentation_enabled ,
94108 suppress_http_instrumentation ,
95109)
96110from opentelemetry .metrics import Histogram , get_meter
97111from opentelemetry .propagate import inject
112+ from opentelemetry .semconv ._incubating .metrics .http_metrics import (
113+ HTTP_CLIENT_REQUEST_BODY_SIZE ,
114+ HTTP_CLIENT_RESPONSE_BODY_SIZE ,
115+ create_http_client_request_body_size ,
116+ create_http_client_response_body_size ,
117+ )
118+ from opentelemetry .semconv .attributes .error_attributes import ERROR_TYPE
98119from opentelemetry .semconv .metrics import MetricInstruments
120+ from opentelemetry .semconv .metrics .http_metrics import (
121+ HTTP_CLIENT_REQUEST_DURATION ,
122+ )
99123from opentelemetry .semconv .trace import SpanAttributes
100124from opentelemetry .trace import Span , SpanKind , get_tracer
101- from opentelemetry .trace .status import Status
102125from opentelemetry .util .http import (
103126 ExcludeList ,
104127 get_excluded_urls ,
105128 parse_excluded_urls ,
106129 remove_url_credentials ,
130+ sanitize_method ,
107131)
108132
109133_excluded_urls_from_env = get_excluded_urls ("URLLIB" )
@@ -133,23 +157,29 @@ def _instrument(self, **kwargs):
133157 ``excluded_urls``: A string containing a comma-delimited
134158 list of regexes used to exclude URLs from tracking
135159 """
160+ # initialize semantic conventions opt-in if needed
161+ _OpenTelemetrySemanticConventionStability ._initialize ()
162+ sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
163+ _OpenTelemetryStabilitySignalType .HTTP ,
164+ )
165+ schema_url = _get_schema_url (sem_conv_opt_in_mode )
136166 tracer_provider = kwargs .get ("tracer_provider" )
137167 tracer = get_tracer (
138168 __name__ ,
139169 __version__ ,
140170 tracer_provider ,
141- schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
171+ schema_url = schema_url ,
142172 )
143173 excluded_urls = kwargs .get ("excluded_urls" )
144174 meter_provider = kwargs .get ("meter_provider" )
145175 meter = get_meter (
146176 __name__ ,
147177 __version__ ,
148178 meter_provider ,
149- schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
179+ schema_url = schema_url ,
150180 )
151181
152- histograms = _create_client_histograms (meter )
182+ histograms = _create_client_histograms (meter , sem_conv_opt_in_mode )
153183
154184 _instrument (
155185 tracer ,
@@ -161,6 +191,7 @@ def _instrument(self, **kwargs):
161191 if excluded_urls is None
162192 else parse_excluded_urls (excluded_urls )
163193 ),
194+ sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
164195 )
165196
166197 def _uninstrument (self , ** kwargs ):
@@ -173,12 +204,14 @@ def uninstrument_opener(
173204 _uninstrument_from (opener , restore_as_bound_func = True )
174205
175206
207+ # pylint: disable=too-many-statements
176208def _instrument (
177209 tracer ,
178210 histograms : Dict [str , Histogram ],
179211 request_hook : _RequestHookT = None ,
180212 response_hook : _ResponseHookT = None ,
181213 excluded_urls : ExcludeList = None ,
214+ sem_conv_opt_in_mode : _HTTPStabilityMode = _HTTPStabilityMode .DEFAULT ,
182215):
183216 """Enables tracing of all requests calls that go through
184217 :code:`urllib.Client._make_request`"""
@@ -214,14 +247,22 @@ def _instrumented_open_call(
214247
215248 method = request .get_method ().upper ()
216249
217- span_name = method . strip ( )
250+ span_name = _get_span_name ( method )
218251
219252 url = remove_url_credentials (url )
220253
221- labels = {
222- SpanAttributes .HTTP_METHOD : method ,
223- SpanAttributes .HTTP_URL : url ,
224- }
254+ data = getattr (request , "data" , None )
255+ request_size = 0 if data is None else len (data )
256+
257+ labels = {}
258+
259+ _set_http_method (
260+ labels ,
261+ method ,
262+ sanitize_method (method ),
263+ sem_conv_opt_in_mode ,
264+ )
265+ _set_http_url (labels , url , sem_conv_opt_in_mode )
225266
226267 with tracer .start_as_current_span (
227268 span_name , kind = SpanKind .CLIENT , attributes = labels
@@ -241,24 +282,50 @@ def _instrumented_open_call(
241282 exception = exc
242283 result = getattr (exc , "file" , None )
243284 finally :
244- elapsed_time = round (( default_timer () - start_time ) * 1000 )
245-
285+ duration_s = default_timer () - start_time
286+ response_size = 0
246287 if result is not None :
288+ response_size = int (result .headers .get ("Content-Length" , 0 ))
247289 code_ = result .getcode ()
248- labels [ SpanAttributes . HTTP_STATUS_CODE ] = str ( code_ )
249-
250- if span . is_recording () and code_ is not None :
251- span . set_attribute ( SpanAttributes . HTTP_STATUS_CODE , code_ )
252- span . set_status ( Status ( http_status_to_status_code ( code_ )) )
290+ # set http status code based on semconv
291+ if code_ :
292+ _set_status_code_attribute (
293+ span , code_ , labels , sem_conv_opt_in_mode
294+ )
253295
254296 ver_ = str (getattr (result , "version" , "" ))
255297 if ver_ :
256- labels [ SpanAttributes . HTTP_FLAVOR ] = (
257- f"{ ver_ [:1 ]} .{ ver_ [:- 1 ]} "
298+ _set_http_network_protocol_version (
299+ labels , f"{ ver_ [:1 ]} .{ ver_ [:- 1 ]} " , sem_conv_opt_in_mode
258300 )
259301
302+ if exception is not None and _report_new (sem_conv_opt_in_mode ):
303+ span .set_attribute (ERROR_TYPE , type (exception ).__qualname__ )
304+ labels [ERROR_TYPE ] = type (exception ).__qualname__
305+
306+ duration_attrs_old = _filter_semconv_duration_attrs (
307+ labels ,
308+ _client_duration_attrs_old ,
309+ _client_duration_attrs_new ,
310+ sem_conv_opt_in_mode = _HTTPStabilityMode .DEFAULT ,
311+ )
312+ duration_attrs_new = _filter_semconv_duration_attrs (
313+ labels ,
314+ _client_duration_attrs_old ,
315+ _client_duration_attrs_new ,
316+ sem_conv_opt_in_mode = _HTTPStabilityMode .HTTP ,
317+ )
318+
319+ duration_attrs_old [SpanAttributes .HTTP_URL ] = url
320+
260321 _record_histograms (
261- histograms , labels , request , result , elapsed_time
322+ histograms ,
323+ duration_attrs_old ,
324+ duration_attrs_new ,
325+ request_size ,
326+ response_size ,
327+ duration_s ,
328+ sem_conv_opt_in_mode ,
262329 )
263330
264331 if callable (response_hook ):
@@ -296,43 +363,108 @@ def _uninstrument_from(instr_root, restore_as_bound_func=False):
296363 setattr (instr_root , instr_func_name , original )
297364
298365
299- def _create_client_histograms (meter ) -> Dict [str , Histogram ]:
300- histograms = {
301- MetricInstruments .HTTP_CLIENT_DURATION : meter .create_histogram (
302- name = MetricInstruments .HTTP_CLIENT_DURATION ,
303- unit = "ms" ,
304- description = "Measures the duration of outbound HTTP requests." ,
305- ),
306- MetricInstruments .HTTP_CLIENT_REQUEST_SIZE : meter .create_histogram (
307- name = MetricInstruments .HTTP_CLIENT_REQUEST_SIZE ,
308- unit = "By" ,
309- description = "Measures the size of HTTP request messages." ,
310- ),
311- MetricInstruments .HTTP_CLIENT_RESPONSE_SIZE : meter .create_histogram (
312- name = MetricInstruments .HTTP_CLIENT_RESPONSE_SIZE ,
313- unit = "By" ,
314- description = "Measures the size of HTTP response messages." ,
315- ),
316- }
366+ def _get_span_name (method : str ) -> str :
367+ method = sanitize_method (method .strip ())
368+ if method == "_OTHER" :
369+ method = "HTTP"
370+ return method
371+
372+
373+ def _set_status_code_attribute (
374+ span : Span ,
375+ status_code : int ,
376+ metric_attributes : dict = None ,
377+ sem_conv_opt_in_mode : _HTTPStabilityMode = _HTTPStabilityMode .DEFAULT ,
378+ ) -> None :
379+
380+ status_code_str = str (status_code )
381+ try :
382+ status_code = int (status_code )
383+ except ValueError :
384+ status_code = - 1
385+
386+ if metric_attributes is None :
387+ metric_attributes = {}
388+
389+ _set_status (
390+ span ,
391+ metric_attributes ,
392+ status_code ,
393+ status_code_str ,
394+ server_span = False ,
395+ sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
396+ )
397+
398+
399+ def _create_client_histograms (
400+ meter , sem_conv_opt_in_mode = _HTTPStabilityMode .DEFAULT
401+ ) -> Dict [str , Histogram ]:
402+ histograms = {}
403+ if _report_old (sem_conv_opt_in_mode ):
404+ histograms [MetricInstruments .HTTP_CLIENT_DURATION ] = (
405+ meter .create_histogram (
406+ name = MetricInstruments .HTTP_CLIENT_DURATION ,
407+ unit = "ms" ,
408+ description = "Measures the duration of the outbound HTTP request" ,
409+ )
410+ )
411+ histograms [MetricInstruments .HTTP_CLIENT_REQUEST_SIZE ] = (
412+ meter .create_histogram (
413+ name = MetricInstruments .HTTP_CLIENT_REQUEST_SIZE ,
414+ unit = "By" ,
415+ description = "Measures the size of HTTP request messages." ,
416+ )
417+ )
418+ histograms [MetricInstruments .HTTP_CLIENT_RESPONSE_SIZE ] = (
419+ meter .create_histogram (
420+ name = MetricInstruments .HTTP_CLIENT_RESPONSE_SIZE ,
421+ unit = "By" ,
422+ description = "Measures the size of HTTP response messages." ,
423+ )
424+ )
425+ if _report_new (sem_conv_opt_in_mode ):
426+ histograms [HTTP_CLIENT_REQUEST_DURATION ] = meter .create_histogram (
427+ name = HTTP_CLIENT_REQUEST_DURATION ,
428+ unit = "s" ,
429+ description = "Duration of HTTP client requests." ,
430+ )
431+ histograms [HTTP_CLIENT_REQUEST_BODY_SIZE ] = (
432+ create_http_client_request_body_size (meter )
433+ )
434+ histograms [HTTP_CLIENT_RESPONSE_BODY_SIZE ] = (
435+ create_http_client_response_body_size (meter )
436+ )
317437
318438 return histograms
319439
320440
321441def _record_histograms (
322- histograms , metric_attributes , request , response , elapsed_time
442+ histograms : Dict [str , Histogram ],
443+ metric_attributes_old : dict ,
444+ metric_attributes_new : dict ,
445+ request_size : int ,
446+ response_size : int ,
447+ duration_s : float ,
448+ sem_conv_opt_in_mode : _HTTPStabilityMode = _HTTPStabilityMode .DEFAULT ,
323449):
324- histograms [MetricInstruments .HTTP_CLIENT_DURATION ].record (
325- elapsed_time , attributes = metric_attributes
326- )
327-
328- data = getattr (request , "data" , None )
329- request_size = 0 if data is None else len (data )
330- histograms [MetricInstruments .HTTP_CLIENT_REQUEST_SIZE ].record (
331- request_size , attributes = metric_attributes
332- )
333-
334- if response is not None :
335- response_size = int (response .headers .get ("Content-Length" , 0 ))
450+ if _report_old (sem_conv_opt_in_mode ):
451+ duration = max (round (duration_s * 1000 ), 0 )
452+ histograms [MetricInstruments .HTTP_CLIENT_DURATION ].record (
453+ duration , attributes = metric_attributes_old
454+ )
455+ histograms [MetricInstruments .HTTP_CLIENT_REQUEST_SIZE ].record (
456+ request_size , attributes = metric_attributes_old
457+ )
336458 histograms [MetricInstruments .HTTP_CLIENT_RESPONSE_SIZE ].record (
337- response_size , attributes = metric_attributes
459+ response_size , attributes = metric_attributes_old
460+ )
461+ if _report_new (sem_conv_opt_in_mode ):
462+ histograms [HTTP_CLIENT_REQUEST_DURATION ].record (
463+ duration_s , attributes = metric_attributes_new
464+ )
465+ histograms [HTTP_CLIENT_REQUEST_BODY_SIZE ].record (
466+ request_size , attributes = metric_attributes_new
467+ )
468+ histograms [HTTP_CLIENT_RESPONSE_BODY_SIZE ].record (
469+ response_size , attributes = metric_attributes_new
338470 )
0 commit comments