views.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import functools
  2. import sentry_sdk
  3. from sentry_sdk.consts import OP
  4. from typing import TYPE_CHECKING
  5. if TYPE_CHECKING:
  6. from typing import Any
  7. try:
  8. from asyncio import iscoroutinefunction
  9. except ImportError:
  10. iscoroutinefunction = None # type: ignore
  11. try:
  12. from sentry_sdk.integrations.django.asgi import wrap_async_view
  13. except (ImportError, SyntaxError):
  14. wrap_async_view = None # type: ignore
  15. def patch_views() -> None:
  16. from django.core.handlers.base import BaseHandler
  17. from django.template.response import SimpleTemplateResponse
  18. from sentry_sdk.integrations.django import DjangoIntegration
  19. old_make_view_atomic = BaseHandler.make_view_atomic
  20. old_render = SimpleTemplateResponse.render
  21. def sentry_patched_render(self: "SimpleTemplateResponse") -> "Any":
  22. with sentry_sdk.start_span(
  23. op=OP.VIEW_RESPONSE_RENDER,
  24. name="serialize response",
  25. origin=DjangoIntegration.origin,
  26. ):
  27. return old_render(self)
  28. @functools.wraps(old_make_view_atomic)
  29. def sentry_patched_make_view_atomic(
  30. self: "Any", *args: "Any", **kwargs: "Any"
  31. ) -> "Any":
  32. callback = old_make_view_atomic(self, *args, **kwargs)
  33. # XXX: The wrapper function is created for every request. Find more
  34. # efficient way to wrap views (or build a cache?)
  35. integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
  36. if integration is not None:
  37. is_async_view = (
  38. iscoroutinefunction is not None
  39. and wrap_async_view is not None
  40. and iscoroutinefunction(callback)
  41. )
  42. if is_async_view:
  43. sentry_wrapped_callback = wrap_async_view(callback)
  44. else:
  45. sentry_wrapped_callback = _wrap_sync_view(callback)
  46. else:
  47. sentry_wrapped_callback = callback
  48. return sentry_wrapped_callback
  49. SimpleTemplateResponse.render = sentry_patched_render
  50. BaseHandler.make_view_atomic = sentry_patched_make_view_atomic
  51. def _wrap_sync_view(callback: "Any") -> "Any":
  52. from sentry_sdk.integrations.django import DjangoIntegration
  53. @functools.wraps(callback)
  54. def sentry_wrapped_callback(request: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  55. current_scope = sentry_sdk.get_current_scope()
  56. if current_scope.transaction is not None:
  57. current_scope.transaction.update_active_thread()
  58. sentry_scope = sentry_sdk.get_isolation_scope()
  59. # set the active thread id to the handler thread for sync views
  60. # this isn't necessary for async views since that runs on main
  61. if sentry_scope.profile is not None:
  62. sentry_scope.profile.update_active_thread_id()
  63. integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
  64. if not integration or not integration.middleware_spans:
  65. return callback(request, *args, **kwargs)
  66. with sentry_sdk.start_span(
  67. op=OP.VIEW_RENDER,
  68. name=request.resolver_match.view_name,
  69. origin=DjangoIntegration.origin,
  70. ):
  71. return callback(request, *args, **kwargs)
  72. return sentry_wrapped_callback