diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e905df43..3bee68be 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,13 @@ Change Log Unreleased ---------- +8.0.2 - 2026-08-21 +------------------ +* Implement ``OpenTelemetryBackend.create_span()``, which was previously a + no-op. This means ``function_trace()`` now actually creates a span for the + OpenTelemetry backend, matching the existing New Relic and Datadog + backends, instead of silently doing nothing. + 8.0.1 - 2025-09-29 ------------------ * Stop using deprecated newrelic function calls that were removed in newrelic 11.0.0 (use newer names instead) diff --git a/edx_django_utils/__init__.py b/edx_django_utils/__init__.py index c000c754..96f8b1d1 100644 --- a/edx_django_utils/__init__.py +++ b/edx_django_utils/__init__.py @@ -2,4 +2,4 @@ EdX utilities for Django Application development.. """ -__version__ = "8.0.1" +__version__ = "8.0.2" diff --git a/edx_django_utils/monitoring/internal/backends.py b/edx_django_utils/monitoring/internal/backends.py index 6a1f1986..d36b79fb 100644 --- a/edx_django_utils/monitoring/internal/backends.py +++ b/edx_django_utils/monitoring/internal/backends.py @@ -124,6 +124,7 @@ def __init__(self): # If import fails, the backend won't be used. from opentelemetry import trace self.otel_trace = trace + self.otel_tracer = trace.get_tracer(__name__) def set_attribute(self, key, value): # Sets the value on the current span, not necessarily the root @@ -134,8 +135,12 @@ def record_exception(self): self.otel_trace.get_current_span().record_exception(sys.exc_info()[1]) def create_span(self, name): - # Currently, this is not implemented. - pass + # Returns a new child span parented to the current span (or a new + # root span if none is active), matching the other backends' + # behavior. The caller is responsible for entering/exiting the + # returned context manager, same as with NewRelicBackend and + # DatadogBackend. + return self.otel_tracer.start_as_current_span(name) def tag_root_span_with_error(self, exception): # Currently, this is not implemented for OTel diff --git a/edx_django_utils/monitoring/tests/test_backends.py b/edx_django_utils/monitoring/tests/test_backends.py index feed6209..7203a06f 100644 --- a/edx_django_utils/monitoring/tests/test_backends.py +++ b/edx_django_utils/monitoring/tests/test_backends.py @@ -7,7 +7,7 @@ import pytest from django.test import TestCase, override_settings -from edx_django_utils.monitoring import record_exception, set_custom_attribute +from edx_django_utils.monitoring import function_trace, record_exception, set_custom_attribute from edx_django_utils.monitoring.internal.backends import configured_backends @@ -151,3 +151,24 @@ def test_record_exception( mock_nr_notice_error.assert_called_once() mock_otel_record_exception.assert_called_once() mock_dd_span.assert_called_once() + + @patch('newrelic.agent.FunctionTrace') + @patch('opentelemetry.trace.NoOpTracer.start_as_current_span') + # Same reasoning as test_set_custom_attribute: patch the concrete + # no-op tracer's method, since get_tracer() only returns something + # usable once called, and there's no active provider in this test. + @patch('ddtrace._trace.tracer.Tracer.trace') + def test_create_span( + self, mock_dd_trace, + mock_otel_start_span, mock_nr_function_trace, + ): + with override_settings(OPENEDX_TELEMETRY=[ + 'edx_django_utils.monitoring.NewRelicBackend', + 'edx_django_utils.monitoring.OpenTelemetryBackend', + 'edx_django_utils.monitoring.DatadogBackend', + ]): + with function_trace('some_function_name'): + pass + mock_nr_function_trace.assert_called_once_with('some_function_name') + mock_otel_start_span.assert_called_once_with('some_function_name') + mock_dd_trace.assert_called_once_with('some_function_name')