Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion edx_django_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
EdX utilities for Django Application development..
"""

__version__ = "8.0.1"
__version__ = "8.0.2"
9 changes: 7 additions & 2 deletions edx_django_utils/monitoring/internal/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
23 changes: 22 additions & 1 deletion edx_django_utils/monitoring/tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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')