signals_handlers.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. from functools import wraps
  2. from django.dispatch import Signal
  3. import sentry_sdk
  4. from sentry_sdk.consts import OP
  5. from sentry_sdk.integrations.django import DJANGO_VERSION
  6. from typing import TYPE_CHECKING
  7. if TYPE_CHECKING:
  8. from collections.abc import Callable
  9. from typing import Any, Union
  10. def _get_receiver_name(receiver: "Callable[..., Any]") -> str:
  11. name = ""
  12. if hasattr(receiver, "__qualname__"):
  13. name = receiver.__qualname__
  14. elif hasattr(receiver, "__name__"): # Python 2.7 has no __qualname__
  15. name = receiver.__name__
  16. elif hasattr(
  17. receiver, "func"
  18. ): # certain functions (like partials) dont have a name
  19. if hasattr(receiver, "func") and hasattr(receiver.func, "__name__"):
  20. name = "partial(<function " + receiver.func.__name__ + ">)"
  21. if (
  22. name == ""
  23. ): # In case nothing was found, return the string representation (this is the slowest case)
  24. return str(receiver)
  25. if hasattr(receiver, "__module__"): # prepend with module, if there is one
  26. name = receiver.__module__ + "." + name
  27. return name
  28. def patch_signals() -> None:
  29. """
  30. Patch django signal receivers to create a span.
  31. This only wraps sync receivers. Django>=5.0 introduced async receivers, but
  32. since we don't create transactions for ASGI Django, we don't wrap them.
  33. """
  34. from sentry_sdk.integrations.django import DjangoIntegration
  35. old_live_receivers = Signal._live_receivers
  36. def _sentry_live_receivers(
  37. self: "Signal", sender: "Any"
  38. ) -> "Union[tuple[list[Callable[..., Any]], list[Callable[..., Any]]], list[Callable[..., Any]]]":
  39. if DJANGO_VERSION >= (5, 0):
  40. sync_receivers, async_receivers = old_live_receivers(self, sender)
  41. else:
  42. sync_receivers = old_live_receivers(self, sender)
  43. async_receivers = []
  44. def sentry_sync_receiver_wrapper(
  45. receiver: "Callable[..., Any]",
  46. ) -> "Callable[..., Any]":
  47. @wraps(receiver)
  48. def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
  49. signal_name = _get_receiver_name(receiver)
  50. with sentry_sdk.start_span(
  51. op=OP.EVENT_DJANGO,
  52. name=signal_name,
  53. origin=DjangoIntegration.origin,
  54. ) as span:
  55. span.set_data("signal", signal_name)
  56. return receiver(*args, **kwargs)
  57. return wrapper
  58. integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
  59. if (
  60. integration
  61. and integration.signals_spans
  62. and self not in integration.signals_denylist
  63. ):
  64. for idx, receiver in enumerate(sync_receivers):
  65. sync_receivers[idx] = sentry_sync_receiver_wrapper(receiver)
  66. if DJANGO_VERSION >= (5, 0):
  67. return sync_receivers, async_receivers
  68. else:
  69. return sync_receivers
  70. Signal._live_receivers = _sentry_live_receivers