Skip to content

Commit dbd7ce8

Browse files
Christophe31Christophe Narbonne
and
Christophe Narbonne
authored
feat: Django rendering monkey patching (getsentry#957)
Co-authored-by: Christophe Narbonne <christophe31@intra.logikap.com>
1 parent b7816b0 commit dbd7ce8

File tree

6 files changed

+84
-1
lines changed

6 files changed

+84
-1
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737

3838

3939
from sentry_sdk.integrations.django.transactions import LEGACY_RESOLVER
40-
from sentry_sdk.integrations.django.templates import get_template_frame_from_exception
40+
from sentry_sdk.integrations.django.templates import (
41+
get_template_frame_from_exception,
42+
patch_templates,
43+
)
4144
from sentry_sdk.integrations.django.middleware import patch_django_middlewares
4245
from sentry_sdk.integrations.django.views import patch_views
4346

@@ -201,6 +204,7 @@ def _django_queryset_repr(value, hint):
201204
_patch_channels()
202205
patch_django_middlewares()
203206
patch_views()
207+
patch_templates()
204208

205209

206210
_DRF_PATCHED = False

sentry_sdk/integrations/django/templates.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.template import TemplateSyntaxError
2+
from django import VERSION as DJANGO_VERSION
23

4+
from sentry_sdk import _functools, Hub
35
from sentry_sdk._types import MYPY
46

57
if MYPY:
@@ -40,6 +42,50 @@ def get_template_frame_from_exception(exc_value):
4042
return None
4143

4244

45+
def patch_templates():
46+
# type: () -> None
47+
from django.template.response import SimpleTemplateResponse
48+
from sentry_sdk.integrations.django import DjangoIntegration
49+
50+
real_rendered_content = SimpleTemplateResponse.rendered_content
51+
52+
@property # type: ignore
53+
def rendered_content(self):
54+
# type: (SimpleTemplateResponse) -> str
55+
hub = Hub.current
56+
if hub.get_integration(DjangoIntegration) is None:
57+
return real_rendered_content.fget(self)
58+
59+
with hub.start_span(
60+
op="django.template.render", description=self.template_name
61+
) as span:
62+
span.set_data("context", self.context_data)
63+
return real_rendered_content.fget(self)
64+
65+
SimpleTemplateResponse.rendered_content = rendered_content
66+
67+
if DJANGO_VERSION < (1, 7):
68+
return
69+
import django.shortcuts
70+
71+
real_render = django.shortcuts.render
72+
73+
@_functools.wraps(real_render)
74+
def render(request, template_name, context=None, *args, **kwargs):
75+
# type: (django.http.HttpRequest, str, Optional[Dict[str, Any]], *Any, **Any) -> django.http.HttpResponse
76+
hub = Hub.current
77+
if hub.get_integration(DjangoIntegration) is None:
78+
return real_render(request, template_name, context, *args, **kwargs)
79+
80+
with hub.start_span(
81+
op="django.template.render", description=template_name
82+
) as span:
83+
span.set_data("context", context)
84+
return real_render(request, template_name, context, *args, **kwargs)
85+
86+
django.shortcuts.render = render
87+
88+
4389
def _get_template_frame_from_debug(debug):
4490
# type: (Dict[str, Any]) -> Dict[str, Any]
4591
if debug is None:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ request.user }}: {{ user_age }}

tests/integrations/django/myapp/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def path(path, *args, **kwargs):
4545
),
4646
path("post-echo", views.post_echo, name="post_echo"),
4747
path("template-exc", views.template_exc, name="template_exc"),
48+
path("template-test", views.template_test, name="template_test"),
49+
path("template-test2", views.template_test2, name="template_test2"),
4850
path(
4951
"permission-denied-exc",
5052
views.permission_denied_exc,

tests/integrations/django/myapp/views.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.core.exceptions import PermissionDenied
55
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseServerError
66
from django.shortcuts import render
7+
from django.template.response import TemplateResponse
78
from django.utils.decorators import method_decorator
89
from django.views.decorators.csrf import csrf_exempt
910
from django.views.generic import ListView
@@ -114,6 +115,16 @@ def template_exc(request, *args, **kwargs):
114115
return render(request, "error.html")
115116

116117

118+
@csrf_exempt
119+
def template_test(request, *args, **kwargs):
120+
return render(request, "user_name.html", {"user_age": 20})
121+
122+
123+
@csrf_exempt
124+
def template_test2(request, *args, **kwargs):
125+
return TemplateResponse(request, "user_name.html", {"user_age": 25})
126+
127+
117128
@csrf_exempt
118129
def permission_denied_exc(*args, **kwargs):
119130
raise PermissionDenied("bye")

tests/integrations/django/test_basic.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,25 @@ def test_does_not_capture_403(sentry_init, client, capture_events, endpoint):
518518
assert not events
519519

520520

521+
def test_render_spans(sentry_init, client, capture_events, render_span_tree):
522+
sentry_init(
523+
integrations=[DjangoIntegration()],
524+
traces_sample_rate=1.0,
525+
)
526+
views_urls = [reverse("template_test2")]
527+
if DJANGO_VERSION >= (1, 7):
528+
views_urls.append(reverse("template_test"))
529+
530+
for url in views_urls:
531+
events = capture_events()
532+
_content, status, _headers = client.get(url)
533+
transaction = events[0]
534+
assert (
535+
'- op="django.template.render": description="user_name.html"'
536+
in render_span_tree(transaction)
537+
)
538+
539+
521540
def test_middleware_spans(sentry_init, client, capture_events, render_span_tree):
522541
sentry_init(
523542
integrations=[DjangoIntegration()],

0 commit comments

Comments
 (0)