Skip to content

Commit 4bf3577

Browse files
authored
Requests instrumentation http semantic convention migration (open-telemetry#2002)
1 parent 4336dc7 commit 4bf3577

File tree

7 files changed

+651
-115
lines changed

7 files changed

+651
-115
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- 'release/*'
77
pull_request:
88
env:
9-
CORE_REPO_SHA: 9831afaff5b4d371fd9a14266ab47884546bd971
9+
CORE_REPO_SHA: 35a021194787359324c46f5ca99d31802e4c92bd
1010

1111
jobs:
1212
build:

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
([#1987](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1987))
1414
- `opentelemetry-instrumentation-httpx` Fix mixing async and non async hooks
1515
([#1920](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1920))
16+
- `opentelemetry-instrumentation-requests` Implement new semantic convention opt-in with stable http semantic conventions
17+
([#2002](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2002))
1618
- `opentelemetry-instrument-grpc` Fix arity of context.abort for AIO RPCs
1719
([#2066](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2066))
1820

1921
### Fixed
2022

2123
- `opentelemetry-instrumentation-urllib`/`opentelemetry-instrumentation-urllib3` Fix metric descriptions to match semantic conventions
22-
([#1959]((https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1959))
24+
([#1959](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1959))
2325

2426
## Version 1.21.0/0.42b0 (2023-11-01)
2527

instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py

Lines changed: 149 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@
6262

6363
# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined.
6464
from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY
65+
from opentelemetry.instrumentation._semconv import (
66+
_METRIC_ATTRIBUTES_CLIENT_DURATION_NAME,
67+
_SPAN_ATTRIBUTES_ERROR_TYPE,
68+
_SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS,
69+
_SPAN_ATTRIBUTES_NETWORK_PEER_PORT,
70+
_filter_duration_attrs,
71+
_get_schema_url,
72+
_OpenTelemetrySemanticConventionStability,
73+
_OpenTelemetryStabilityMode,
74+
_OpenTelemetryStabilitySignalType,
75+
_report_new,
76+
_report_old,
77+
_set_http_hostname,
78+
_set_http_method,
79+
_set_http_net_peer_name,
80+
_set_http_network_protocol_version,
81+
_set_http_port,
82+
_set_http_scheme,
83+
_set_http_status_code,
84+
_set_http_url,
85+
)
6586
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
6687
from opentelemetry.instrumentation.requests.package import _instruments
6788
from opentelemetry.instrumentation.requests.version import __version__
@@ -72,15 +93,15 @@
7293
from opentelemetry.metrics import Histogram, get_meter
7394
from opentelemetry.propagate import inject
7495
from opentelemetry.semconv.metrics import MetricInstruments
75-
from opentelemetry.semconv.trace import SpanAttributes
7696
from opentelemetry.trace import SpanKind, Tracer, get_tracer
7797
from opentelemetry.trace.span import Span
78-
from opentelemetry.trace.status import Status
98+
from opentelemetry.trace.status import StatusCode
7999
from opentelemetry.util.http import (
80100
ExcludeList,
81101
get_excluded_urls,
82102
parse_excluded_urls,
83103
remove_url_credentials,
104+
sanitize_method,
84105
)
85106
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
86107

@@ -94,10 +115,12 @@
94115
# pylint: disable=R0915
95116
def _instrument(
96117
tracer: Tracer,
97-
duration_histogram: Histogram,
118+
duration_histogram_old: Histogram,
119+
duration_histogram_new: Histogram,
98120
request_hook: _RequestHookT = None,
99121
response_hook: _ResponseHookT = None,
100122
excluded_urls: ExcludeList = None,
123+
sem_conv_opt_in_mode: _OpenTelemetryStabilityMode = _OpenTelemetryStabilityMode.DEFAULT,
101124
):
102125
"""Enables tracing of all requests calls that go through
103126
:code:`requests.session.Session.request` (this includes
@@ -132,31 +155,58 @@ def get_or_create_headers():
132155
return wrapped_send(self, request, **kwargs)
133156

134157
# See
135-
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client
136-
method = request.method.upper()
158+
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
159+
method = request.method
137160
span_name = get_default_span_name(method)
138161

139162
url = remove_url_credentials(request.url)
140163

141-
span_attributes = {
142-
SpanAttributes.HTTP_METHOD: method,
143-
SpanAttributes.HTTP_URL: url,
144-
}
164+
span_attributes = {}
165+
_set_http_method(
166+
span_attributes, method, span_name, sem_conv_opt_in_mode
167+
)
168+
_set_http_url(span_attributes, url, sem_conv_opt_in_mode)
145169

146-
metric_labels = {
147-
SpanAttributes.HTTP_METHOD: method,
148-
}
170+
metric_labels = {}
171+
_set_http_method(
172+
metric_labels, method, span_name, sem_conv_opt_in_mode
173+
)
149174

150175
try:
151176
parsed_url = urlparse(url)
152-
metric_labels[SpanAttributes.HTTP_SCHEME] = parsed_url.scheme
177+
if parsed_url.scheme:
178+
_set_http_scheme(
179+
metric_labels, parsed_url.scheme, sem_conv_opt_in_mode
180+
)
153181
if parsed_url.hostname:
154-
metric_labels[SpanAttributes.HTTP_HOST] = parsed_url.hostname
155-
metric_labels[
156-
SpanAttributes.NET_PEER_NAME
157-
] = parsed_url.hostname
182+
_set_http_hostname(
183+
metric_labels, parsed_url.hostname, sem_conv_opt_in_mode
184+
)
185+
_set_http_net_peer_name(
186+
metric_labels, parsed_url.hostname, sem_conv_opt_in_mode
187+
)
188+
if _report_new(sem_conv_opt_in_mode):
189+
_set_http_hostname(
190+
span_attributes,
191+
parsed_url.hostname,
192+
sem_conv_opt_in_mode,
193+
)
194+
# Use semconv library when available
195+
span_attributes[
196+
_SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS
197+
] = parsed_url.hostname
158198
if parsed_url.port:
159-
metric_labels[SpanAttributes.NET_PEER_PORT] = parsed_url.port
199+
_set_http_port(
200+
metric_labels, parsed_url.port, sem_conv_opt_in_mode
201+
)
202+
if _report_new(sem_conv_opt_in_mode):
203+
_set_http_port(
204+
span_attributes, parsed_url.port, sem_conv_opt_in_mode
205+
)
206+
# Use semconv library when available
207+
span_attributes[
208+
_SPAN_ATTRIBUTES_NETWORK_PEER_PORT
209+
] = parsed_url.port
160210
except ValueError:
161211
pass
162212

@@ -182,35 +232,78 @@ def get_or_create_headers():
182232
exception = exc
183233
result = getattr(exc, "response", None)
184234
finally:
185-
elapsed_time = max(
186-
round((default_timer() - start_time) * 1000), 0
187-
)
235+
elapsed_time = max(default_timer() - start_time, 0)
188236
context.detach(token)
189237

190238
if isinstance(result, Response):
239+
span_attributes = {}
191240
if span.is_recording():
192-
span.set_attribute(
193-
SpanAttributes.HTTP_STATUS_CODE, result.status_code
241+
_set_http_status_code(
242+
span_attributes,
243+
result.status_code,
244+
sem_conv_opt_in_mode,
194245
)
195-
span.set_status(
196-
Status(http_status_to_status_code(result.status_code))
246+
_set_http_status_code(
247+
metric_labels, result.status_code, sem_conv_opt_in_mode
197248
)
198-
199-
metric_labels[
200-
SpanAttributes.HTTP_STATUS_CODE
201-
] = result.status_code
249+
status_code = http_status_to_status_code(
250+
result.status_code
251+
)
252+
span.set_status(status_code)
253+
if (
254+
_report_new(sem_conv_opt_in_mode)
255+
and status_code is StatusCode.ERROR
256+
):
257+
span_attributes[_SPAN_ATTRIBUTES_ERROR_TYPE] = str(
258+
result.status_code
259+
)
260+
metric_labels[_SPAN_ATTRIBUTES_ERROR_TYPE] = str(
261+
result.status_code
262+
)
202263

203264
if result.raw is not None:
204265
version = getattr(result.raw, "version", None)
205266
if version:
206-
metric_labels[SpanAttributes.HTTP_FLAVOR] = (
207-
"1.1" if version == 11 else "1.0"
267+
# Only HTTP/1 is supported by requests
268+
version_text = "1.1" if version == 11 else "1.0"
269+
_set_http_network_protocol_version(
270+
metric_labels, version_text, sem_conv_opt_in_mode
208271
)
272+
if _report_new(sem_conv_opt_in_mode):
273+
_set_http_network_protocol_version(
274+
span_attributes,
275+
version_text,
276+
sem_conv_opt_in_mode,
277+
)
278+
for key, val in span_attributes.items():
279+
span.set_attribute(key, val)
209280

210281
if callable(response_hook):
211282
response_hook(span, request, result)
212283

213-
duration_histogram.record(elapsed_time, attributes=metric_labels)
284+
if exception is not None and _report_new(sem_conv_opt_in_mode):
285+
span.set_attribute(
286+
_SPAN_ATTRIBUTES_ERROR_TYPE, type(exception).__qualname__
287+
)
288+
metric_labels[_SPAN_ATTRIBUTES_ERROR_TYPE] = type(
289+
exception
290+
).__qualname__
291+
292+
if duration_histogram_old is not None:
293+
duration_attrs_old = _filter_duration_attrs(
294+
metric_labels, _OpenTelemetryStabilityMode.DEFAULT
295+
)
296+
duration_histogram_old.record(
297+
max(round(elapsed_time * 1000), 0),
298+
attributes=duration_attrs_old,
299+
)
300+
if duration_histogram_new is not None:
301+
duration_attrs_new = _filter_duration_attrs(
302+
metric_labels, _OpenTelemetryStabilityMode.HTTP
303+
)
304+
duration_histogram_new.record(
305+
elapsed_time, attributes=duration_attrs_new
306+
)
214307

215308
if exception is not None:
216309
raise exception.with_traceback(exception.__traceback__)
@@ -254,7 +347,7 @@ def get_default_span_name(method):
254347
Returns:
255348
span name
256349
"""
257-
return method.strip()
350+
return sanitize_method(method.upper().strip())
258351

259352

260353
class RequestsInstrumentor(BaseInstrumentor):
@@ -276,34 +369,49 @@ def _instrument(self, **kwargs):
276369
``excluded_urls``: A string containing a comma-delimited
277370
list of regexes used to exclude URLs from tracking
278371
"""
372+
semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
373+
_OpenTelemetryStabilitySignalType.HTTP,
374+
)
375+
schema_url = _get_schema_url(semconv_opt_in_mode)
279376
tracer_provider = kwargs.get("tracer_provider")
280377
tracer = get_tracer(
281378
__name__,
282379
__version__,
283380
tracer_provider,
284-
schema_url="https://opentelemetry.io/schemas/1.11.0",
381+
schema_url=schema_url,
285382
)
286383
excluded_urls = kwargs.get("excluded_urls")
287384
meter_provider = kwargs.get("meter_provider")
288385
meter = get_meter(
289386
__name__,
290387
__version__,
291388
meter_provider,
292-
schema_url="https://opentelemetry.io/schemas/1.11.0",
293-
)
294-
duration_histogram = meter.create_histogram(
295-
name=MetricInstruments.HTTP_CLIENT_DURATION,
296-
unit="ms",
297-
description="measures the duration of the outbound HTTP request",
389+
schema_url=schema_url,
298390
)
391+
duration_histogram_old = None
392+
if _report_old(semconv_opt_in_mode):
393+
duration_histogram_old = meter.create_histogram(
394+
name=MetricInstruments.HTTP_CLIENT_DURATION,
395+
unit="ms",
396+
description="measures the duration of the outbound HTTP request",
397+
)
398+
duration_histogram_new = None
399+
if _report_new(semconv_opt_in_mode):
400+
duration_histogram_new = meter.create_histogram(
401+
name=_METRIC_ATTRIBUTES_CLIENT_DURATION_NAME,
402+
unit="s",
403+
description="Duration of HTTP client requests.",
404+
)
299405
_instrument(
300406
tracer,
301-
duration_histogram,
407+
duration_histogram_old,
408+
duration_histogram_new,
302409
request_hook=kwargs.get("request_hook"),
303410
response_hook=kwargs.get("response_hook"),
304411
excluded_urls=_excluded_urls_from_env
305412
if excluded_urls is None
306413
else parse_excluded_urls(excluded_urls),
414+
sem_conv_opt_in_mode=semconv_opt_in_mode,
307415
)
308416

309417
def _uninstrument(self, **kwargs):

0 commit comments

Comments
 (0)