views.py 3.1 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():
  16. # type: () -> None
  17. from django.core.handlers.base import BaseHandler
  18. from django.template.response import SimpleTemplateResponse
  19. from sentry_sdk.integrations.django import DjangoIntegration
  20. old_make_view_atomic = BaseHandler.make_view_atomic
  21. old_render = SimpleTemplateResponse.render
  22. def sentry_patched_render(self):
  23. # type: (SimpleTemplateResponse) -> Any
  24. with sentry_sdk.start_span(
  25. op=OP.VIEW_RESPONSE_RENDER,
  26. name="serialize response",
  27. origin=DjangoIntegration.origin,
  28. ):
  29. return old_render(self)
  30. @functools.wraps(old_make_view_atomic)
  31. def sentry_patched_make_view_atomic(self, *args, **kwargs):
  32. # type: (Any, *Any, **Any) -> Any
  33. callback = old_make_view_atomic(self, *args, **kwargs)
  34. # XXX: The wrapper function is created for every request. Find more
  35. # efficient way to wrap views (or build a cache?)
  36. integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
  37. if integration is not None and integration.middleware_spans:
  38. is_async_view = (
  39. iscoroutinefunction is not None
  40. and wrap_async_view is not None
  41. and iscoroutinefunction(callback)
  42. )
  43. if is_async_view:
  44. sentry_wrapped_callback = wrap_async_view(callback)
  45. else:
  46. sentry_wrapped_callback = _wrap_sync_view(callback)
  47. else:
  48. sentry_wrapped_callback = callback
  49. return sentry_wrapped_callback
  50. SimpleTemplateResponse.render = sentry_patched_render
  51. BaseHandler.make_view_atomic = sentry_patched_make_view_atomic
  52. def _wrap_sync_view(callback):
  53. # type: (Any) -> Any
  54. from sentry_sdk.integrations.django import DjangoIntegration
  55. @functools.wraps(callback)
  56. def sentry_wrapped_callback(request, *args, **kwargs):
  57. # type: (Any, *Any, **Any) -> Any
  58. current_scope = sentry_sdk.get_current_scope()
  59. if current_scope.transaction is not None:
  60. current_scope.transaction.update_active_thread()
  61. sentry_scope = sentry_sdk.get_isolation_scope()
  62. # set the active thread id to the handler thread for sync views
  63. # this isn't necessary for async views since that runs on main
  64. if sentry_scope.profile is not None:
  65. sentry_scope.profile.update_active_thread_id()
  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