Skip to content

Commit aa53419

Browse files
Django activation exit after metrics record/add
1 parent 86d2481 commit aa53419

File tree

2 files changed

+76
-39
lines changed

2 files changed

+76
-39
lines changed

instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
)
5656
from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE
5757
from opentelemetry.semconv.trace import SpanAttributes
58-
from opentelemetry.trace import Span, SpanKind, set_span_in_context, use_span
58+
from opentelemetry.trace import Span, SpanKind, use_span
5959
from opentelemetry.util.http import (
6060
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
6161
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
@@ -349,6 +349,7 @@ def process_response(self, request, response):
349349

350350
activation = request.META.pop(self._environ_activation_key, None)
351351
span = request.META.pop(self._environ_span_key, None)
352+
exception = None
352353
active_requests_count_attrs = request.META.pop(
353354
self._environ_active_request_attr_key, None
354355
)
@@ -417,20 +418,7 @@ def process_response(self, request, response):
417418
except Exception: # pylint: disable=broad-exception-caught
418419
_logger.exception("Exception raised by response_hook")
419420

420-
if exception:
421-
activation.__exit__(
422-
type(exception),
423-
exception,
424-
getattr(exception, "__traceback__", None),
425-
)
426-
else:
427-
activation.__exit__(None, None, None)
428-
429421
if request_start_time is not None:
430-
# Get the span and re-create context manually to pass to
431-
# histogram for exemplars generation
432-
metrics_context = set_span_in_context(span)
433-
434422
duration_s = default_timer() - request_start_time
435423
if self._duration_histogram_old:
436424
duration_attrs_old = _parse_duration_attrs(
@@ -443,7 +431,6 @@ def process_response(self, request, response):
443431
self._duration_histogram_old.record(
444432
max(round(duration_s * 1000), 0),
445433
duration_attrs_old,
446-
context=metrics_context,
447434
)
448435
if self._duration_histogram_new:
449436
duration_attrs_new = _parse_duration_attrs(
@@ -452,9 +439,19 @@ def process_response(self, request, response):
452439
self._duration_histogram_new.record(
453440
max(duration_s, 0),
454441
duration_attrs_new,
455-
context=metrics_context,
456442
)
457443
self._active_request_counter.add(-1, active_requests_count_attrs)
444+
445+
if activation and span:
446+
if exception:
447+
activation.__exit__(
448+
type(exception),
449+
exception,
450+
getattr(exception, "__traceback__", None),
451+
)
452+
else:
453+
activation.__exit__(None, None, None)
454+
458455
if request.META.get(self._environ_token, None) is not None:
459456
detach(request.META.get(self._environ_token))
460457
request.META.pop(self._environ_token)

instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -917,29 +917,6 @@ def test_wsgi_metrics_both_semconv(self):
917917
)
918918
self.assertTrue(histrogram_data_point_seen and number_data_point_seen)
919919

920-
def test_wsgi_metrics_context_propagation(self):
921-
with (
922-
patch.object(
923-
_DjangoMiddleware,
924-
"_duration_histogram_old",
925-
) as mock_histogram_old,
926-
patch.object(
927-
_DjangoMiddleware,
928-
"_duration_histogram_new",
929-
) as mock_histogram_new,
930-
):
931-
mock_histogram_old.record = Mock()
932-
mock_histogram_new.record = Mock()
933-
934-
Client().get("/traced/")
935-
936-
self.assertTrue(mock_histogram_old.record.called)
937-
call_args = mock_histogram_old.record.call_args
938-
self.assertIsNotNone(call_args)
939-
self.assertIn("context", call_args.kwargs)
940-
context_arg = call_args.kwargs["context"]
941-
self.assertIsNotNone(context_arg)
942-
943920
def test_wsgi_metrics_unistrument(self):
944921
Client().get("/span_name/1234/")
945922
_django_instrumentor.uninstrument()
@@ -1127,3 +1104,66 @@ def test_http_custom_response_headers_not_in_span_attributes(self):
11271104
for key, _ in not_expected.items():
11281105
self.assertNotIn(key, span.attributes)
11291106
self.memory_exporter.clear()
1107+
1108+
1109+
class TestMiddlewareSpanActivationTiming(WsgiTestBase):
1110+
"""Test span activation timing relative to metrics recording."""
1111+
1112+
@classmethod
1113+
def setUpClass(cls):
1114+
conf.settings.configure(ROOT_URLCONF=modules[__name__])
1115+
super().setUpClass()
1116+
1117+
def setUp(self):
1118+
super().setUp()
1119+
setup_test_environment()
1120+
_django_instrumentor.instrument()
1121+
1122+
def tearDown(self):
1123+
super().tearDown()
1124+
teardown_test_environment()
1125+
_django_instrumentor.uninstrument()
1126+
1127+
@classmethod
1128+
def tearDownClass(cls):
1129+
super().tearDownClass()
1130+
conf.settings = conf.LazySettings()
1131+
1132+
def test_span_ended_after_metrics_recorded(self):
1133+
"""Span activation exits after metrics recording."""
1134+
Client().get("/traced/")
1135+
1136+
spans = self.memory_exporter.get_finished_spans()
1137+
self.assertEqual(len(spans), 1)
1138+
1139+
# Span properly finished
1140+
self.assertIsNotNone(spans[0].end_time)
1141+
1142+
# Metrics recorded
1143+
metrics_list = self.memory_metrics_reader.get_metrics_data()
1144+
histogram_found = any(
1145+
"duration" in metric.name
1146+
for rm in metrics_list.resource_metrics
1147+
for sm in rm.scope_metrics
1148+
for metric in sm.metrics
1149+
)
1150+
self.assertTrue(histogram_found)
1151+
1152+
def test_metrics_recorded_with_exception(self):
1153+
"""Metrics recorded even when request raises exception."""
1154+
with self.assertRaises(ValueError):
1155+
Client().get("/error/")
1156+
1157+
spans = self.memory_exporter.get_finished_spans()
1158+
self.assertEqual(len(spans), 1)
1159+
self.assertEqual(spans[0].status.status_code, StatusCode.ERROR)
1160+
1161+
# Metrics still recorded
1162+
metrics_list = self.memory_metrics_reader.get_metrics_data()
1163+
histogram_found = any(
1164+
"duration" in metric.name
1165+
for rm in metrics_list.resource_metrics
1166+
for sm in rm.scope_metrics
1167+
for metric in sm.metrics
1168+
)
1169+
self.assertTrue(histogram_found)

0 commit comments

Comments
 (0)