templates.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import functools
  2. from django.template import TemplateSyntaxError
  3. from django.utils.safestring import mark_safe
  4. from django import VERSION as DJANGO_VERSION
  5. import sentry_sdk
  6. from sentry_sdk.consts import OP
  7. from sentry_sdk.utils import ensure_integration_enabled
  8. from typing import TYPE_CHECKING
  9. if TYPE_CHECKING:
  10. from typing import Any
  11. from typing import Dict
  12. from typing import Optional
  13. from typing import Iterator
  14. from typing import Tuple
  15. try:
  16. # support Django 1.9
  17. from django.template.base import Origin
  18. except ImportError:
  19. # backward compatibility
  20. from django.template.loader import LoaderOrigin as Origin
  21. def get_template_frame_from_exception(
  22. exc_value: "Optional[BaseException]",
  23. ) -> "Optional[Dict[str, Any]]":
  24. # As of Django 1.9 or so the new template debug thing showed up.
  25. if hasattr(exc_value, "template_debug"):
  26. return _get_template_frame_from_debug(exc_value.template_debug) # type: ignore
  27. # As of r16833 (Django) all exceptions may contain a
  28. # ``django_template_source`` attribute (rather than the legacy
  29. # ``TemplateSyntaxError.source`` check)
  30. if hasattr(exc_value, "django_template_source"):
  31. return _get_template_frame_from_source(
  32. exc_value.django_template_source # type: ignore
  33. )
  34. if isinstance(exc_value, TemplateSyntaxError) and hasattr(exc_value, "source"):
  35. source = exc_value.source
  36. if isinstance(source, (tuple, list)) and isinstance(source[0], Origin):
  37. return _get_template_frame_from_source(source) # type: ignore
  38. return None
  39. def _get_template_name_description(template_name: str) -> str:
  40. if isinstance(template_name, (list, tuple)):
  41. if template_name:
  42. return "[{}, ...]".format(template_name[0])
  43. else:
  44. return template_name
  45. def patch_templates() -> None:
  46. from django.template.response import SimpleTemplateResponse
  47. from sentry_sdk.integrations.django import DjangoIntegration
  48. real_rendered_content = SimpleTemplateResponse.rendered_content
  49. @property # type: ignore
  50. @ensure_integration_enabled(DjangoIntegration, real_rendered_content.fget)
  51. def rendered_content(self: "SimpleTemplateResponse") -> str:
  52. with sentry_sdk.start_span(
  53. op=OP.TEMPLATE_RENDER,
  54. name=_get_template_name_description(self.template_name),
  55. origin=DjangoIntegration.origin,
  56. ) as span:
  57. span.set_data("context", self.context_data)
  58. return real_rendered_content.fget(self)
  59. SimpleTemplateResponse.rendered_content = rendered_content
  60. if DJANGO_VERSION < (1, 7):
  61. return
  62. import django.shortcuts
  63. real_render = django.shortcuts.render
  64. @functools.wraps(real_render)
  65. @ensure_integration_enabled(DjangoIntegration, real_render)
  66. def render(
  67. request: "django.http.HttpRequest",
  68. template_name: str,
  69. context: "Optional[Dict[str, Any]]" = None,
  70. *args: "Any",
  71. **kwargs: "Any",
  72. ) -> "django.http.HttpResponse":
  73. # Inject trace meta tags into template context
  74. context = context or {}
  75. if "sentry_trace_meta" not in context:
  76. context["sentry_trace_meta"] = mark_safe(
  77. sentry_sdk.get_current_scope().trace_propagation_meta()
  78. )
  79. with sentry_sdk.start_span(
  80. op=OP.TEMPLATE_RENDER,
  81. name=_get_template_name_description(template_name),
  82. origin=DjangoIntegration.origin,
  83. ) as span:
  84. span.set_data("context", context)
  85. return real_render(request, template_name, context, *args, **kwargs)
  86. django.shortcuts.render = render
  87. def _get_template_frame_from_debug(debug: "Dict[str, Any]") -> "Dict[str, Any]":
  88. if debug is None:
  89. return None
  90. lineno = debug["line"]
  91. filename = debug["name"]
  92. if filename is None:
  93. filename = "<django template>"
  94. pre_context = []
  95. post_context = []
  96. context_line = None
  97. for i, line in debug["source_lines"]:
  98. if i < lineno:
  99. pre_context.append(line)
  100. elif i > lineno:
  101. post_context.append(line)
  102. else:
  103. context_line = line
  104. return {
  105. "filename": filename,
  106. "lineno": lineno,
  107. "pre_context": pre_context[-5:],
  108. "post_context": post_context[:5],
  109. "context_line": context_line,
  110. "in_app": True,
  111. }
  112. def _linebreak_iter(template_source: str) -> "Iterator[int]":
  113. yield 0
  114. p = template_source.find("\n")
  115. while p >= 0:
  116. yield p + 1
  117. p = template_source.find("\n", p + 1)
  118. def _get_template_frame_from_source(
  119. source: "Tuple[Origin, Tuple[int, int]]",
  120. ) -> "Optional[Dict[str, Any]]":
  121. if not source:
  122. return None
  123. origin, (start, end) = source
  124. filename = getattr(origin, "loadname", None)
  125. if filename is None:
  126. filename = "<django template>"
  127. template_source = origin.reload()
  128. lineno = None
  129. upto = 0
  130. pre_context = []
  131. post_context = []
  132. context_line = None
  133. for num, next in enumerate(_linebreak_iter(template_source)):
  134. line = template_source[upto:next]
  135. if start >= upto and end <= next:
  136. lineno = num
  137. context_line = line
  138. elif lineno is None:
  139. pre_context.append(line)
  140. else:
  141. post_context.append(line)
  142. upto = next
  143. if context_line is None or lineno is None:
  144. return None
  145. return {
  146. "filename": filename,
  147. "lineno": lineno,
  148. "pre_context": pre_context[-5:],
  149. "post_context": post_context[:5],
  150. "context_line": context_line,
  151. }