Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 8f348bb

Browse files
authored
Add exception tracing to django middleware (#885)
1 parent a82fa83 commit 8f348bb

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

contrib/opencensus-ext-django/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Add exception tracing to django middleware
6+
([#885](https://github.com/census-instrumentation/opencensus-python/pull/885))
7+
58
## 0.7.4
69
Released 2021-01-19
710

contrib/opencensus-ext-django/opencensus/ext/django/middleware.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import six
1717

1818
import logging
19+
import sys
20+
import traceback
1921

2022
import django
2123
import django.conf
@@ -42,6 +44,9 @@
4244
HTTP_ROUTE = attributes_helper.COMMON_ATTRIBUTES['HTTP_ROUTE']
4345
HTTP_URL = attributes_helper.COMMON_ATTRIBUTES['HTTP_URL']
4446
HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES['HTTP_STATUS_CODE']
47+
ERROR_MESSAGE = attributes_helper.COMMON_ATTRIBUTES['ERROR_MESSAGE']
48+
ERROR_NAME = attributes_helper.COMMON_ATTRIBUTES['ERROR_NAME']
49+
STACKTRACE = attributes_helper.COMMON_ATTRIBUTES['STACKTRACE']
4550

4651
REQUEST_THREAD_LOCAL_KEY = 'django_request'
4752
SPAN_THREAD_LOCAL_KEY = 'django_span'
@@ -267,3 +272,29 @@ def process_response(self, request, response):
267272
log.error('Failed to trace request', exc_info=True)
268273
finally:
269274
return response
275+
276+
def process_exception(self, request, exception):
277+
# Do not trace if the url is excluded
278+
if utils.disable_tracing_url(request.path, self.excludelist_paths):
279+
return
280+
281+
try:
282+
if hasattr(exception, '__traceback__'):
283+
tb = exception.__traceback__
284+
else:
285+
_, _, tb = sys.exc_info()
286+
287+
span = _get_django_span()
288+
span.add_attribute(
289+
attribute_key=ERROR_NAME,
290+
attribute_value=exception.__class__.__name__)
291+
span.add_attribute(
292+
attribute_key=ERROR_MESSAGE,
293+
attribute_value=str(exception))
294+
span.add_attribute(
295+
attribute_key=STACKTRACE,
296+
attribute_value='\n'.join(traceback.format_tb(tb)))
297+
298+
_set_django_attributes(span, request)
299+
except Exception: # pragma: NO COVER
300+
log.error('Failed to trace request', exc_info=True)

contrib/opencensus-ext-django/tests/test_django_middleware.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import sys
16+
import traceback
1517
import unittest
1618

1719
import mock
@@ -291,6 +293,73 @@ def test_process_response_unfinished_child_span(self):
291293

292294
self.assertEqual(span.attributes, expected_attributes)
293295

296+
def test_process_exception(self):
297+
from opencensus.ext.django import middleware
298+
299+
trace_id = '2dd43a1d6b2549c6bc2a1a54c2fc0b05'
300+
span_id = '6e0c63257de34c92'
301+
django_trace_id = '00-{}-{}-00'.format(trace_id, span_id)
302+
303+
django_request = RequestFactory().get('/wiki/Rabbit', **{
304+
'traceparent': django_trace_id,
305+
})
306+
307+
# Force the test request to be sampled
308+
settings = type('Test', (object,), {})
309+
settings.OPENCENSUS = {
310+
'TRACE': {
311+
'SAMPLER': 'opencensus.trace.samplers.AlwaysOnSampler()', # noqa
312+
}
313+
}
314+
patch_settings = mock.patch(
315+
'django.conf.settings',
316+
settings)
317+
318+
with patch_settings:
319+
middleware_obj = middleware.OpencensusMiddleware()
320+
321+
tb = None
322+
try:
323+
raise RuntimeError("bork bork bork")
324+
except Exception as exc:
325+
test_exception = exc
326+
if hasattr(exc, "__traceback__"):
327+
tb = exc.__traceback__
328+
else:
329+
_, _, tb = sys.exc_info()
330+
331+
middleware_obj.process_request(django_request)
332+
tracer = middleware._get_current_tracer()
333+
span = tracer.current_span()
334+
335+
exporter_mock = mock.Mock()
336+
tracer.exporter = exporter_mock
337+
338+
django_response = mock.Mock()
339+
django_response.status_code = 200
340+
341+
expected_attributes = {
342+
'http.host': u'testserver',
343+
'http.method': 'GET',
344+
'http.path': u'/wiki/Rabbit',
345+
'http.route': u'/wiki/Rabbit',
346+
'http.url': u'http://testserver/wiki/Rabbit',
347+
'django.user.id': '123',
348+
'django.user.name': 'test_name',
349+
'error.name': "RuntimeError",
350+
'error.message': 'bork bork bork',
351+
'stacktrace': '\n'.join(traceback.format_tb(tb))
352+
}
353+
354+
mock_user = mock.Mock()
355+
mock_user.pk = 123
356+
mock_user.get_username.return_value = 'test_name'
357+
django_request.user = mock_user
358+
359+
middleware_obj.process_exception(django_request, test_exception)
360+
361+
self.assertEqual(span.attributes, expected_attributes)
362+
294363

295364
class Test__set_django_attributes(unittest.TestCase):
296365
class Span(object):

contrib/opencensus-ext-sqlalchemy/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
long_description=open('README.rst').read(),
4141
install_requires=[
4242
'opencensus >= 0.8.dev0, < 1.0.0',
43-
'SQLAlchemy >= 1.1.14, < 2.0.0',
43+
'SQLAlchemy >= 1.1.14, < 1.3.24', # https://github.com/sqlalchemy/sqlalchemy/issues/6168 # noqa: E501
4444
],
4545
extras_require={},
4646
license='Apache-2.0',

0 commit comments

Comments
 (0)