fastapi.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import asyncio
  2. from copy import deepcopy
  3. from functools import wraps
  4. import sentry_sdk
  5. from sentry_sdk.integrations import DidNotEnable
  6. from sentry_sdk.scope import should_send_default_pii
  7. from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
  8. from sentry_sdk.utils import transaction_from_function
  9. from typing import TYPE_CHECKING
  10. if TYPE_CHECKING:
  11. from typing import Any, Callable, Dict
  12. from sentry_sdk._types import Event
  13. try:
  14. from sentry_sdk.integrations.starlette import (
  15. StarletteIntegration,
  16. StarletteRequestExtractor,
  17. )
  18. except DidNotEnable:
  19. raise DidNotEnable("Starlette is not installed")
  20. try:
  21. import fastapi # type: ignore
  22. except ImportError:
  23. raise DidNotEnable("FastAPI is not installed")
  24. _DEFAULT_TRANSACTION_NAME = "generic FastAPI request"
  25. class FastApiIntegration(StarletteIntegration):
  26. identifier = "fastapi"
  27. @staticmethod
  28. def setup_once() -> None:
  29. patch_get_request_handler()
  30. def _set_transaction_name_and_source(
  31. scope: "sentry_sdk.Scope", transaction_style: str, request: "Any"
  32. ) -> None:
  33. name = ""
  34. if transaction_style == "endpoint":
  35. endpoint = request.scope.get("endpoint")
  36. if endpoint:
  37. name = transaction_from_function(endpoint) or ""
  38. elif transaction_style == "url":
  39. route = request.scope.get("route")
  40. if route:
  41. path = getattr(route, "path", None)
  42. if path is not None:
  43. name = path
  44. if not name:
  45. name = _DEFAULT_TRANSACTION_NAME
  46. source = TransactionSource.ROUTE
  47. else:
  48. source = SOURCE_FOR_STYLE[transaction_style]
  49. scope.set_transaction_name(name, source=source)
  50. def patch_get_request_handler() -> None:
  51. old_get_request_handler = fastapi.routing.get_request_handler
  52. def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any":
  53. dependant = kwargs.get("dependant")
  54. if (
  55. dependant
  56. and dependant.call is not None
  57. and not asyncio.iscoroutinefunction(dependant.call)
  58. ):
  59. old_call = dependant.call
  60. @wraps(old_call)
  61. def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any":
  62. current_scope = sentry_sdk.get_current_scope()
  63. if current_scope.transaction is not None:
  64. current_scope.transaction.update_active_thread()
  65. sentry_scope = sentry_sdk.get_isolation_scope()
  66. if sentry_scope.profile is not None:
  67. sentry_scope.profile.update_active_thread_id()
  68. return old_call(*args, **kwargs)
  69. dependant.call = _sentry_call
  70. old_app = old_get_request_handler(*args, **kwargs)
  71. async def _sentry_app(*args: "Any", **kwargs: "Any") -> "Any":
  72. integration = sentry_sdk.get_client().get_integration(FastApiIntegration)
  73. if integration is None:
  74. return await old_app(*args, **kwargs)
  75. request = args[0]
  76. _set_transaction_name_and_source(
  77. sentry_sdk.get_current_scope(), integration.transaction_style, request
  78. )
  79. sentry_scope = sentry_sdk.get_isolation_scope()
  80. extractor = StarletteRequestExtractor(request)
  81. info = await extractor.extract_request_info()
  82. def _make_request_event_processor(
  83. req: "Any", integration: "Any"
  84. ) -> "Callable[[Event, Dict[str, Any]], Event]":
  85. def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
  86. # Extract information from request
  87. request_info = event.get("request", {})
  88. if info:
  89. if "cookies" in info and should_send_default_pii():
  90. request_info["cookies"] = info["cookies"]
  91. if "data" in info:
  92. request_info["data"] = info["data"]
  93. event["request"] = deepcopy(request_info)
  94. return event
  95. return event_processor
  96. sentry_scope._name = FastApiIntegration.identifier
  97. sentry_scope.add_event_processor(
  98. _make_request_event_processor(request, integration)
  99. )
  100. return await old_app(*args, **kwargs)
  101. return _sentry_app
  102. fastapi.routing.get_request_handler = _sentry_get_request_handler