Skip to content

Commit 000db25

Browse files
authored
Merge pull request #451 from openedx/diana/add-error-tagging
feat: Add root span tagging for exceptions.
2 parents 51b2d4a + 9342700 commit 000db25

File tree

6 files changed

+77
-9
lines changed

6 files changed

+77
-9
lines changed

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ Change Log
1111

1212
.. There should always be an "Unreleased" section for changes pending release.
1313
14+
[5.16.0] - 2024-09-27
15+
---------------------
16+
Added
17+
~~~~~
18+
* Added a new method to backends for ``tag_root_span_with_error`` and added Datadog implementation of the functionality.
19+
* Uses the new method to tag the root span when processing exceptions.
20+
21+
Changed
22+
~~~~~~~
23+
* Renamed ``CachedCustomMonitoringMiddleware`` to ``MonitoringSupportMiddleware`` and deprecated the old name. It will be removed in a future release.
24+
25+
1426
[5.15.0] - 2024-07-29
1527
---------------------
1628
Added

edx_django_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
EdX utilities for Django Application development..
33
"""
44

5-
__version__ = "5.15.0"
5+
__version__ = "5.16.0"
66

77
default_app_config = (
88
"edx_django_utils.apps.EdxDjangoUtilsConfig"

edx_django_utils/monitoring/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
CookieMonitoringMiddleware,
1616
DeploymentMonitoringMiddleware,
1717
FrontendMonitoringMiddleware,
18-
MonitoringMemoryMiddleware
18+
MonitoringMemoryMiddleware,
19+
MonitoringSupportMiddleware
1920
)
2021
from .internal.transactions import get_current_transaction, ignore_transaction, set_monitoring_transaction_name
2122
from .internal.utils import (

edx_django_utils/monitoring/internal/backends.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ def create_span(self, name):
6262
or create a new root span if not currently in a span.
6363
"""
6464

65+
@abstractmethod
66+
def tag_root_span_with_error(self, exception):
67+
"""
68+
Tags the root span with the given exception. This is primarily useful for
69+
Datadog as New Relic handles this behavior correctly. Unclear if this is also needs to
70+
be implemented for OTEL.
71+
"""
72+
6573

6674
class NewRelicBackend(TelemetryBackend):
6775
"""
@@ -96,6 +104,10 @@ def create_span(self, name):
96104
nr_transaction = newrelic.agent.current_transaction()
97105
return newrelic.agent.FunctionTrace(nr_transaction, name)
98106

107+
def tag_root_span_with_error(self, exception):
108+
# Does not need to be implemented for NewRelic, because it is handled automatically.
109+
pass
110+
99111

100112
class OpenTelemetryBackend(TelemetryBackend):
101113
"""
@@ -121,6 +133,10 @@ def create_span(self, name):
121133
# Currently, this is not implemented.
122134
pass
123135

136+
def tag_root_span_with_error(self, exception):
137+
# Currently, this is not implemented for OTel
138+
pass
139+
124140

125141
class DatadogBackend(TelemetryBackend):
126142
"""
@@ -145,6 +161,10 @@ def record_exception(self):
145161
def create_span(self, name):
146162
return self.dd_tracer.trace(name)
147163

164+
def tag_root_span_with_error(self, exception):
165+
root_span = self.dd_tracer.current_root_span()
166+
root_span.set_exc_info(type(exception), exception, exception.__traceback__)
167+
148168

149169
# We're using an lru_cache instead of assigning the result to a variable on
150170
# module load. With the default settings (pointing to a TelemetryBackend

edx_django_utils/monitoring/internal/middleware.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ def record_python_version():
7070
_set_custom_attribute('python_version', platform.python_version())
7171

7272

73-
class CachedCustomMonitoringMiddleware(MiddlewareMixin):
73+
class MonitoringSupportMiddleware(MiddlewareMixin):
7474
"""
75-
Middleware batch reports cached custom attributes at the end of a request.
75+
Middleware to support monitoring.
76+
77+
1. Middleware batch reports cached custom attributes at the end of a request.
78+
2. Middleware adds error span tags to the root span.
7679
7780
Make sure to add below the request cache in MIDDLEWARE.
7881
@@ -130,6 +133,13 @@ def _batch_report(cls):
130133
for key, value in attributes_cache.data.items():
131134
_set_custom_attribute(key, value)
132135

136+
def _tag_root_span_with_error(self, exception):
137+
"""
138+
Tags the root span with the exception information for all configured backends.
139+
"""
140+
for backend in configured_backends():
141+
backend.tag_root_span_with_error(exception)
142+
133143
# Whether or not there was an exception, report any custom NR attributes that
134144
# may have been collected.
135145

@@ -145,6 +155,16 @@ def process_exception(self, request, exception): # pylint: disable=W0613
145155
Django middleware handler to process an exception
146156
"""
147157
self._batch_report()
158+
self._tag_root_span_with_error(exception)
159+
160+
161+
class CachedCustomMonitoringMiddleware(MonitoringSupportMiddleware):
162+
"""
163+
DEPRECATED: Use MonitoringSupportMiddleware instead.
164+
165+
This is the old name for the MonitoringSupportMiddleware. We are keeping it
166+
around for backwards compatibility until it can be fully removed.
167+
"""
148168

149169

150170
def _set_custom_attribute(key, value):

edx_django_utils/monitoring/tests/test_custom_monitoring.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
from unittest.mock import Mock, call, patch
77

88
import ddt
9-
from django.test import TestCase
9+
from django.test import TestCase, override_settings
1010

1111
from edx_django_utils.cache import RequestCache
12-
from edx_django_utils.monitoring import CachedCustomMonitoringMiddleware, accumulate, get_current_transaction, increment
12+
from edx_django_utils.monitoring import MonitoringSupportMiddleware, accumulate, get_current_transaction, increment
1313

1414
from ..middleware import CachedCustomMonitoringMiddleware as DeprecatedCachedCustomMonitoringMiddleware
1515
from ..middleware import MonitoringCustomMetricsMiddleware as DeprecatedMonitoringCustomMetricsMiddleware
@@ -31,8 +31,8 @@ def setUp(self):
3131

3232
@patch('newrelic.agent')
3333
@ddt.data(
34-
(CachedCustomMonitoringMiddleware, False, 'process_response'),
35-
(CachedCustomMonitoringMiddleware, False, 'process_exception'),
34+
(MonitoringSupportMiddleware, False, 'process_response'),
35+
(MonitoringSupportMiddleware, False, 'process_exception'),
3636
(DeprecatedCachedCustomMonitoringMiddleware, True, 'process_response'),
3737
(DeprecatedMonitoringCustomMetricsMiddleware, True, 'process_response'),
3838
)
@@ -76,7 +76,7 @@ def test_accumulate_and_increment(
7676

7777
@patch('newrelic.agent')
7878
@ddt.data(
79-
(CachedCustomMonitoringMiddleware, False),
79+
(MonitoringSupportMiddleware, False),
8080
(DeprecatedCachedCustomMonitoringMiddleware, True),
8181
(DeprecatedMonitoringCustomMetricsMiddleware, True),
8282
)
@@ -113,6 +113,21 @@ def test_accumulate_with_illegal_value(
113113
# Assert call args to newrelic.agent.add_custom_parameter().
114114
mock_newrelic_agent.add_custom_parameter.assert_has_calls(nr_agent_calls_expected, any_order=True)
115115

116+
@patch('ddtrace.Tracer.current_root_span')
117+
def test_error_tagging(self, mock_get_root_span):
118+
# Ensure that we continue to support tagging exceptions in MonitoringSupportMiddleware.
119+
# This is only implemented for DatadogBackend at the moment.
120+
fake_exception = Exception()
121+
mock_root_span = Mock()
122+
mock_get_root_span.return_value = mock_root_span
123+
with override_settings(OPENEDX_TELEMETRY=['edx_django_utils.monitoring.DatadogBackend']):
124+
MonitoringSupportMiddleware(self.mock_response).process_exception(
125+
'fake request', fake_exception
126+
)
127+
mock_root_span.set_exc_info.assert_called_with(
128+
type(fake_exception), fake_exception, fake_exception.__traceback__
129+
)
130+
116131
@patch('newrelic.agent')
117132
def test_get_current_transaction(self, mock_newrelic_agent):
118133
mock_newrelic_agent.current_transaction().name = 'fake-transaction'

0 commit comments

Comments
 (0)