middleware.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. """
  2. Create spans from Django middleware invocations
  3. """
  4. from functools import wraps
  5. from django import VERSION as DJANGO_VERSION
  6. import sentry_sdk
  7. from sentry_sdk.consts import OP
  8. from sentry_sdk.utils import (
  9. ContextVar,
  10. transaction_from_function,
  11. capture_internal_exceptions,
  12. )
  13. from typing import TYPE_CHECKING
  14. if TYPE_CHECKING:
  15. from typing import Any
  16. from typing import Callable
  17. from typing import Optional
  18. from typing import TypeVar
  19. from sentry_sdk.tracing import Span
  20. F = TypeVar("F", bound=Callable[..., Any])
  21. _import_string_should_wrap_middleware = ContextVar(
  22. "import_string_should_wrap_middleware"
  23. )
  24. DJANGO_SUPPORTS_ASYNC_MIDDLEWARE = DJANGO_VERSION >= (3, 1)
  25. if not DJANGO_SUPPORTS_ASYNC_MIDDLEWARE:
  26. _asgi_middleware_mixin_factory = lambda _: object
  27. else:
  28. from .asgi import _asgi_middleware_mixin_factory
  29. def patch_django_middlewares():
  30. # type: () -> None
  31. from django.core.handlers import base
  32. old_import_string = base.import_string
  33. def sentry_patched_import_string(dotted_path):
  34. # type: (str) -> Any
  35. rv = old_import_string(dotted_path)
  36. if _import_string_should_wrap_middleware.get(None):
  37. rv = _wrap_middleware(rv, dotted_path)
  38. return rv
  39. base.import_string = sentry_patched_import_string
  40. old_load_middleware = base.BaseHandler.load_middleware
  41. def sentry_patched_load_middleware(*args, **kwargs):
  42. # type: (Any, Any) -> Any
  43. _import_string_should_wrap_middleware.set(True)
  44. try:
  45. return old_load_middleware(*args, **kwargs)
  46. finally:
  47. _import_string_should_wrap_middleware.set(False)
  48. base.BaseHandler.load_middleware = sentry_patched_load_middleware
  49. def _wrap_middleware(middleware, middleware_name):
  50. # type: (Any, str) -> Any
  51. from sentry_sdk.integrations.django import DjangoIntegration
  52. def _check_middleware_span(old_method):
  53. # type: (Callable[..., Any]) -> Optional[Span]
  54. integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
  55. if integration is None or not integration.middleware_spans:
  56. return None
  57. function_name = transaction_from_function(old_method)
  58. description = middleware_name
  59. function_basename = getattr(old_method, "__name__", None)
  60. if function_basename:
  61. description = "{}.{}".format(description, function_basename)
  62. middleware_span = sentry_sdk.start_span(
  63. op=OP.MIDDLEWARE_DJANGO,
  64. name=description,
  65. origin=DjangoIntegration.origin,
  66. )
  67. middleware_span.set_tag("django.function_name", function_name)
  68. middleware_span.set_tag("django.middleware_name", middleware_name)
  69. return middleware_span
  70. def _get_wrapped_method(old_method):
  71. # type: (F) -> F
  72. with capture_internal_exceptions():
  73. def sentry_wrapped_method(*args, **kwargs):
  74. # type: (*Any, **Any) -> Any
  75. middleware_span = _check_middleware_span(old_method)
  76. if middleware_span is None:
  77. return old_method(*args, **kwargs)
  78. with middleware_span:
  79. return old_method(*args, **kwargs)
  80. try:
  81. # fails for __call__ of function on Python 2 (see py2.7-django-1.11)
  82. sentry_wrapped_method = wraps(old_method)(sentry_wrapped_method)
  83. # Necessary for Django 3.1
  84. sentry_wrapped_method.__self__ = old_method.__self__ # type: ignore
  85. except Exception:
  86. pass
  87. return sentry_wrapped_method # type: ignore
  88. return old_method
  89. class SentryWrappingMiddleware(
  90. _asgi_middleware_mixin_factory(_check_middleware_span) # type: ignore
  91. ):
  92. sync_capable = getattr(middleware, "sync_capable", True)
  93. async_capable = DJANGO_SUPPORTS_ASYNC_MIDDLEWARE and getattr(
  94. middleware, "async_capable", False
  95. )
  96. def __init__(self, get_response=None, *args, **kwargs):
  97. # type: (Optional[Callable[..., Any]], *Any, **Any) -> None
  98. if get_response:
  99. self._inner = middleware(get_response, *args, **kwargs)
  100. else:
  101. self._inner = middleware(*args, **kwargs)
  102. self.get_response = get_response
  103. self._call_method = None
  104. if self.async_capable:
  105. super().__init__(get_response)
  106. # We need correct behavior for `hasattr()`, which we can only determine
  107. # when we have an instance of the middleware we're wrapping.
  108. def __getattr__(self, method_name):
  109. # type: (str) -> Any
  110. if method_name not in (
  111. "process_request",
  112. "process_view",
  113. "process_template_response",
  114. "process_response",
  115. "process_exception",
  116. ):
  117. raise AttributeError()
  118. old_method = getattr(self._inner, method_name)
  119. rv = _get_wrapped_method(old_method)
  120. self.__dict__[method_name] = rv
  121. return rv
  122. def __call__(self, *args, **kwargs):
  123. # type: (*Any, **Any) -> Any
  124. if hasattr(self, "async_route_check") and self.async_route_check():
  125. return self.__acall__(*args, **kwargs)
  126. f = self._call_method
  127. if f is None:
  128. self._call_method = f = self._inner.__call__
  129. middleware_span = _check_middleware_span(old_method=f)
  130. if middleware_span is None:
  131. return f(*args, **kwargs)
  132. with middleware_span:
  133. return f(*args, **kwargs)
  134. for attr in (
  135. "__name__",
  136. "__module__",
  137. "__qualname__",
  138. ):
  139. if hasattr(middleware, attr):
  140. setattr(SentryWrappingMiddleware, attr, getattr(middleware, attr))
  141. return SentryWrappingMiddleware