Skip to content

Commit 23cadea

Browse files
emdnetolzchenshalevr
authored
requests: always record span status code in duration metrics (#3323)
* show test fail at main Signed-off-by: emdneto <[email protected]> * implement fix -- ci should pass now Signed-off-by: emdneto <[email protected]> * remove uneeded comment Signed-off-by: emdneto <[email protected]> * Update instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py * use get_sorted_metrics Signed-off-by: emdneto <[email protected]> --------- Signed-off-by: emdneto <[email protected]> Co-authored-by: Leighton Chen <[email protected]> Co-authored-by: Shalev Roda <[email protected]>
1 parent 2371adf commit 23cadea

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

CHANGELOG.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
- `opentelemetry-instrumentation-openai-v2` Update doc for OpenAI Instrumentation to support OpenAI Compatible Platforms
1717
([#3279](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3279))
18-
- `opentelemetry-instrumentation-system-metrics` Add `process` metrics and deprecated `process.runtime` prefixed ones
18+
- `opentelemetry-instrumentation-system-metrics` Add `process` metrics and deprecated `process.runtime` prefixed ones
1919
([#3250](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3250))
2020
- `opentelemetry-instrumentation-botocore` Add support for GenAI user events and lazy initialize tracer
2121
([#3258](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3258))
@@ -41,6 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4141
([#3247](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3247))
4242
- `opentelemetry-instrumentation-asyncpg` Fix fallback for empty queries.
4343
([#3253](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3253))
44+
- `opentelemetry-instrumentation-requests` always record span status code in duration metric
45+
([#3323](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3323))
4446

4547
## Version 1.30.0/0.51b0 (2025-02-03)
4648

@@ -100,7 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
100102

101103
### Breaking changes
102104

103-
- `opentelemetry-exporter-prometheus-remote-write` updated protobuf required version from 4.21 to 5.26 and regenerated protobufs
105+
- `opentelemetry-exporter-prometheus-remote-write` updated protobuf required version from 4.21 to 5.26 and regenerated protobufs
104106
([#3219](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3219))
105107
- `opentelemetry-instrumentation-sqlalchemy` including sqlcomment in `db.statement` span attribute value is now opt-in
106108
([#3112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3112))

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

+33-22
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,14 @@ def response_hook(span, request_obj, response):
101101
_set_http_network_protocol_version,
102102
_set_http_peer_port_client,
103103
_set_http_scheme,
104-
_set_http_status_code,
105104
_set_http_url,
105+
_set_status,
106106
_StabilityMode,
107107
)
108108
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
109109
from opentelemetry.instrumentation.requests.package import _instruments
110110
from opentelemetry.instrumentation.requests.version import __version__
111111
from opentelemetry.instrumentation.utils import (
112-
http_status_to_status_code,
113112
is_http_instrumentation_enabled,
114113
suppress_http_instrumentation,
115114
)
@@ -126,7 +125,6 @@ def response_hook(span, request_obj, response):
126125
)
127126
from opentelemetry.trace import SpanKind, Tracer, get_tracer
128127
from opentelemetry.trace.span import Span
129-
from opentelemetry.trace.status import StatusCode
130128
from opentelemetry.util.http import (
131129
ExcludeList,
132130
get_excluded_urls,
@@ -142,6 +140,32 @@ def response_hook(span, request_obj, response):
142140
_ResponseHookT = Optional[Callable[[Span, PreparedRequest, Response], None]]
143141

144142

143+
def _set_http_status_code_attribute(
144+
span,
145+
status_code,
146+
metric_attributes=None,
147+
sem_conv_opt_in_mode=_StabilityMode.DEFAULT,
148+
):
149+
status_code_str = str(status_code)
150+
try:
151+
status_code = int(status_code)
152+
except ValueError:
153+
status_code = -1
154+
if metric_attributes is None:
155+
metric_attributes = {}
156+
# When we have durations we should set metrics only once
157+
# Also the decision to include status code on a histogram should
158+
# not be dependent on tracing decisions.
159+
_set_status(
160+
span,
161+
metric_attributes,
162+
status_code,
163+
status_code_str,
164+
server_span=False,
165+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
166+
)
167+
168+
145169
# pylint: disable=unused-argument
146170
# pylint: disable=R0915
147171
def _instrument(
@@ -269,25 +293,12 @@ def get_or_create_headers():
269293

270294
if isinstance(result, Response):
271295
span_attributes = {}
272-
if span.is_recording():
273-
_set_http_status_code(
274-
span_attributes,
275-
result.status_code,
276-
sem_conv_opt_in_mode,
277-
)
278-
_set_http_status_code(
279-
metric_labels, result.status_code, sem_conv_opt_in_mode
280-
)
281-
status_code = http_status_to_status_code(
282-
result.status_code
283-
)
284-
span.set_status(status_code)
285-
if (
286-
_report_new(sem_conv_opt_in_mode)
287-
and status_code is StatusCode.ERROR
288-
):
289-
span_attributes[ERROR_TYPE] = str(result.status_code)
290-
metric_labels[ERROR_TYPE] = str(result.status_code)
296+
_set_http_status_code_attribute(
297+
span,
298+
result.status_code,
299+
metric_labels,
300+
sem_conv_opt_in_mode,
301+
)
291302

292303
if result.raw is not None:
293304
version = getattr(result.raw, "version", None)

instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py

+31
Original file line numberDiff line numberDiff line change
@@ -832,3 +832,34 @@ def test_basic_metric_both_semconv(self):
832832
dict(data_point.attributes),
833833
)
834834
self.assertEqual(data_point.count, 1)
835+
836+
def test_basic_metric_non_recording_span(self):
837+
expected_attributes = {
838+
SpanAttributes.HTTP_STATUS_CODE: 200,
839+
SpanAttributes.HTTP_HOST: "examplehost",
840+
SpanAttributes.NET_PEER_PORT: 8000,
841+
SpanAttributes.NET_PEER_NAME: "examplehost",
842+
SpanAttributes.HTTP_METHOD: "GET",
843+
SpanAttributes.HTTP_FLAVOR: "1.1",
844+
SpanAttributes.HTTP_SCHEME: "http",
845+
}
846+
847+
with mock.patch("opentelemetry.trace.INVALID_SPAN") as mock_span:
848+
RequestsInstrumentor().uninstrument()
849+
RequestsInstrumentor().instrument(
850+
tracer_provider=trace.NoOpTracerProvider()
851+
)
852+
mock_span.is_recording.return_value = False
853+
result = self.perform_request(self.URL)
854+
self.assertEqual(result.text, "Hello!")
855+
self.assertFalse(mock_span.is_recording())
856+
self.assertTrue(mock_span.is_recording.called)
857+
self.assertFalse(mock_span.set_attribute.called)
858+
self.assertFalse(mock_span.set_status.called)
859+
metrics = self.get_sorted_metrics()
860+
self.assertEqual(len(metrics), 1)
861+
duration_data_point = metrics[0].data.data_points[0]
862+
self.assertDictEqual(
863+
expected_attributes, dict(duration_data_point.attributes)
864+
)
865+
self.assertEqual(duration_data_point.count, 1)

0 commit comments

Comments
 (0)