sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,11 @@
1
- from __future__ import absolute_import
2
-
3
- from sentry_sdk import Hub
4
- from sentry_sdk._types import MYPY
1
+ import sentry_sdk
5
2
  from sentry_sdk.integrations import Integration, DidNotEnable
6
3
  from sentry_sdk.scope import add_global_event_processor
7
4
  from sentry_sdk.utils import walk_exception_chain, iter_stacks
8
5
 
9
- if MYPY:
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
10
9
  from typing import Optional
11
10
 
12
11
  from sentry_sdk._types import Event, Hint
@@ -27,7 +26,7 @@ class ExecutingIntegration(Integration):
27
26
  @add_global_event_processor
28
27
  def add_executing_info(event, hint):
29
28
  # type: (Event, Optional[Hint]) -> Optional[Event]
30
- if Hub.current.get_integration(ExecutingIntegration) is None:
29
+ if sentry_sdk.get_client().get_integration(ExecutingIntegration) is None:
31
30
  return event
32
31
 
33
32
  if hint is None:
@@ -1,28 +1,53 @@
1
- from __future__ import absolute_import
2
-
3
- from sentry_sdk.hub import Hub
4
- from sentry_sdk.integrations import Integration, DidNotEnable
1
+ import sentry_sdk
2
+ from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
5
3
  from sentry_sdk.integrations._wsgi_common import RequestExtractor
6
4
  from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
7
- from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
5
+ from sentry_sdk.tracing import SOURCE_FOR_STYLE
6
+ from sentry_sdk.utils import (
7
+ capture_internal_exceptions,
8
+ ensure_integration_enabled,
9
+ event_from_exception,
10
+ parse_version,
11
+ )
8
12
 
9
- from sentry_sdk._types import MYPY
13
+ from typing import TYPE_CHECKING
10
14
 
11
- if MYPY:
15
+ if TYPE_CHECKING:
12
16
  from typing import Any
13
17
  from typing import Dict
14
18
  from typing import Optional
15
19
 
16
- from sentry_sdk._types import EventProcessor
20
+ from sentry_sdk._types import Event, EventProcessor
21
+
22
+ # In Falcon 3.0 `falcon.api_helpers` is renamed to `falcon.app_helpers`
23
+ # and `falcon.API` to `falcon.App`
17
24
 
18
25
  try:
19
26
  import falcon # type: ignore
20
- import falcon.api_helpers # type: ignore
21
27
 
22
28
  from falcon import __version__ as FALCON_VERSION
23
29
  except ImportError:
24
30
  raise DidNotEnable("Falcon not installed")
25
31
 
32
+ try:
33
+ import falcon.app_helpers # type: ignore
34
+
35
+ falcon_helpers = falcon.app_helpers
36
+ falcon_app_class = falcon.App
37
+ FALCON3 = True
38
+ except ImportError:
39
+ import falcon.api_helpers # type: ignore
40
+
41
+ falcon_helpers = falcon.api_helpers
42
+ falcon_app_class = falcon.API
43
+ FALCON3 = False
44
+
45
+
46
+ _FALCON_UNSET = None # type: Optional[object]
47
+ if FALCON3: # falcon.request._UNSET is only available in Falcon 3.0+
48
+ with capture_internal_exceptions():
49
+ from falcon.request import _UNSET as _FALCON_UNSET # type: ignore[import-not-found, no-redef]
50
+
26
51
 
27
52
  class FalconRequestExtractor(RequestExtractor):
28
53
  def env(self):
@@ -56,29 +81,35 @@ class FalconRequestExtractor(RequestExtractor):
56
81
 
57
82
  def json(self):
58
83
  # type: () -> Optional[Dict[str, Any]]
59
- try:
60
- return self.request.media
61
- except falcon.errors.HTTPBadRequest:
62
- # NOTE(jmagnusson): We return `falcon.Request._media` here because
63
- # falcon 1.4 doesn't do proper type checking in
64
- # `falcon.Request.media`. This has been fixed in 2.0.
65
- # Relevant code: https://github.com/falconry/falcon/blob/1.4.1/falcon/request.py#L953
66
- return self.request._media
84
+ # fallback to cached_media = None if self.request._media is not available
85
+ cached_media = None
86
+ with capture_internal_exceptions():
87
+ # self.request._media is the cached self.request.media
88
+ # value. It is only available if self.request.media
89
+ # has already been accessed. Therefore, reading
90
+ # self.request._media will not exhaust the raw request
91
+ # stream (self.request.bounded_stream) because it has
92
+ # already been read if self.request._media is set.
93
+ cached_media = self.request._media
94
+
95
+ if cached_media is not _FALCON_UNSET:
96
+ return cached_media
67
97
 
98
+ return None
68
99
 
69
- class SentryFalconMiddleware(object):
100
+
101
+ class SentryFalconMiddleware:
70
102
  """Captures exceptions in Falcon requests and send to Sentry"""
71
103
 
72
104
  def process_request(self, req, resp, *args, **kwargs):
73
105
  # type: (Any, Any, *Any, **Any) -> None
74
- hub = Hub.current
75
- integration = hub.get_integration(FalconIntegration)
106
+ integration = sentry_sdk.get_client().get_integration(FalconIntegration)
76
107
  if integration is None:
77
108
  return
78
109
 
79
- with hub.configure_scope() as scope:
80
- scope._name = "falcon"
81
- scope.add_event_processor(_make_request_event_processor(req, integration))
110
+ scope = sentry_sdk.get_isolation_scope()
111
+ scope._name = "falcon"
112
+ scope.add_event_processor(_make_request_event_processor(req, integration))
82
113
 
83
114
 
84
115
  TRANSACTION_STYLE_VALUES = ("uri_template", "path")
@@ -86,8 +117,9 @@ TRANSACTION_STYLE_VALUES = ("uri_template", "path")
86
117
 
87
118
  class FalconIntegration(Integration):
88
119
  identifier = "falcon"
120
+ origin = f"auto.http.{identifier}"
89
121
 
90
- transaction_style = None
122
+ transaction_style = ""
91
123
 
92
124
  def __init__(self, transaction_style="uri_template"):
93
125
  # type: (str) -> None
@@ -101,13 +133,9 @@ class FalconIntegration(Integration):
101
133
  @staticmethod
102
134
  def setup_once():
103
135
  # type: () -> None
104
- try:
105
- version = tuple(map(int, FALCON_VERSION.split(".")))
106
- except (ValueError, TypeError):
107
- raise DidNotEnable("Unparsable Falcon version: {}".format(FALCON_VERSION))
108
136
 
109
- if version < (1, 4):
110
- raise DidNotEnable("Falcon 1.4 or newer required.")
137
+ version = parse_version(FALCON_VERSION)
138
+ _check_minimum_version(FalconIntegration, version)
111
139
 
112
140
  _patch_wsgi_app()
113
141
  _patch_handle_exception()
@@ -116,94 +144,129 @@ class FalconIntegration(Integration):
116
144
 
117
145
  def _patch_wsgi_app():
118
146
  # type: () -> None
119
- original_wsgi_app = falcon.API.__call__
147
+ original_wsgi_app = falcon_app_class.__call__
120
148
 
121
149
  def sentry_patched_wsgi_app(self, env, start_response):
122
150
  # type: (falcon.API, Any, Any) -> Any
123
- hub = Hub.current
124
- integration = hub.get_integration(FalconIntegration)
151
+ integration = sentry_sdk.get_client().get_integration(FalconIntegration)
125
152
  if integration is None:
126
153
  return original_wsgi_app(self, env, start_response)
127
154
 
128
155
  sentry_wrapped = SentryWsgiMiddleware(
129
- lambda envi, start_resp: original_wsgi_app(self, envi, start_resp)
156
+ lambda envi, start_resp: original_wsgi_app(self, envi, start_resp),
157
+ span_origin=FalconIntegration.origin,
130
158
  )
131
159
 
132
160
  return sentry_wrapped(env, start_response)
133
161
 
134
- falcon.API.__call__ = sentry_patched_wsgi_app
162
+ falcon_app_class.__call__ = sentry_patched_wsgi_app
135
163
 
136
164
 
137
165
  def _patch_handle_exception():
138
166
  # type: () -> None
139
- original_handle_exception = falcon.API._handle_exception
167
+ original_handle_exception = falcon_app_class._handle_exception
140
168
 
169
+ @ensure_integration_enabled(FalconIntegration, original_handle_exception)
141
170
  def sentry_patched_handle_exception(self, *args):
142
171
  # type: (falcon.API, *Any) -> Any
143
172
  # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception
144
173
  # method signature from `(ex, req, resp, params)` to
145
174
  # `(req, resp, ex, params)`
146
- if isinstance(args[0], Exception):
147
- ex = args[0]
148
- else:
149
- ex = args[2]
175
+ ex = response = None
176
+ with capture_internal_exceptions():
177
+ ex = next(argument for argument in args if isinstance(argument, Exception))
178
+ response = next(
179
+ argument for argument in args if isinstance(argument, falcon.Response)
180
+ )
150
181
 
151
182
  was_handled = original_handle_exception(self, *args)
152
183
 
153
- hub = Hub.current
154
- integration = hub.get_integration(FalconIntegration)
155
-
156
- if integration is not None and not _is_falcon_http_error(ex):
157
- # If an integration is there, a client has to be there.
158
- client = hub.client # type: Any
184
+ if ex is None or response is None:
185
+ # Both ex and response should have a non-None value at this point; otherwise,
186
+ # there is an error with the SDK that will have been captured in the
187
+ # capture_internal_exceptions block above.
188
+ return was_handled
159
189
 
190
+ if _exception_leads_to_http_5xx(ex, response):
160
191
  event, hint = event_from_exception(
161
192
  ex,
162
- client_options=client.options,
193
+ client_options=sentry_sdk.get_client().options,
163
194
  mechanism={"type": "falcon", "handled": False},
164
195
  )
165
- hub.capture_event(event, hint=hint)
196
+ sentry_sdk.capture_event(event, hint=hint)
166
197
 
167
198
  return was_handled
168
199
 
169
- falcon.API._handle_exception = sentry_patched_handle_exception
200
+ falcon_app_class._handle_exception = sentry_patched_handle_exception
170
201
 
171
202
 
172
203
  def _patch_prepare_middleware():
173
204
  # type: () -> None
174
- original_prepare_middleware = falcon.api_helpers.prepare_middleware
205
+ original_prepare_middleware = falcon_helpers.prepare_middleware
175
206
 
176
207
  def sentry_patched_prepare_middleware(
177
- middleware=None, independent_middleware=False
208
+ middleware=None, independent_middleware=False, asgi=False
178
209
  ):
179
- # type: (Any, Any) -> Any
180
- hub = Hub.current
181
- integration = hub.get_integration(FalconIntegration)
210
+ # type: (Any, Any, bool) -> Any
211
+ if asgi:
212
+ # We don't support ASGI Falcon apps, so we don't patch anything here
213
+ return original_prepare_middleware(middleware, independent_middleware, asgi)
214
+
215
+ integration = sentry_sdk.get_client().get_integration(FalconIntegration)
182
216
  if integration is not None:
183
217
  middleware = [SentryFalconMiddleware()] + (middleware or [])
218
+
219
+ # We intentionally omit the asgi argument here, since the default is False anyways,
220
+ # and this way, we remain backwards-compatible with pre-3.0.0 Falcon versions.
184
221
  return original_prepare_middleware(middleware, independent_middleware)
185
222
 
186
- falcon.api_helpers.prepare_middleware = sentry_patched_prepare_middleware
223
+ falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware
224
+
225
+
226
+ def _exception_leads_to_http_5xx(ex, response):
227
+ # type: (Exception, falcon.Response) -> bool
228
+ is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or "").startswith(
229
+ "5"
230
+ )
231
+ is_unhandled_error = not isinstance(
232
+ ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)
233
+ )
234
+
235
+ # We only check the HTTP status on Falcon 3 because in Falcon 2, the status on the response
236
+ # at the stage where we capture it is listed as 200, even though we would expect to see a 500
237
+ # status. Since at the time of this change, Falcon 2 is ca. 4 years old, we have decided to
238
+ # only perform this check on Falcon 3+, despite the risk that some handled errors might be
239
+ # reported to Sentry as unhandled on Falcon 2.
240
+ return (is_server_error or is_unhandled_error) and (
241
+ not FALCON3 or _has_http_5xx_status(response)
242
+ )
243
+
244
+
245
+ def _has_http_5xx_status(response):
246
+ # type: (falcon.Response) -> bool
247
+ return response.status.startswith("5")
187
248
 
188
249
 
189
- def _is_falcon_http_error(ex):
190
- # type: (BaseException) -> bool
191
- return isinstance(ex, (falcon.HTTPError, falcon.http_status.HTTPStatus))
250
+ def _set_transaction_name_and_source(event, transaction_style, request):
251
+ # type: (Event, str, falcon.Request) -> None
252
+ name_for_style = {
253
+ "uri_template": request.uri_template,
254
+ "path": request.path,
255
+ }
256
+ event["transaction"] = name_for_style[transaction_style]
257
+ event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
192
258
 
193
259
 
194
260
  def _make_request_event_processor(req, integration):
195
261
  # type: (falcon.Request, FalconIntegration) -> EventProcessor
196
262
 
197
- def inner(event, hint):
198
- # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
199
- if integration.transaction_style == "uri_template":
200
- event["transaction"] = req.uri_template
201
- elif integration.transaction_style == "path":
202
- event["transaction"] = req.path
263
+ def event_processor(event, hint):
264
+ # type: (Event, dict[str, Any]) -> Event
265
+ _set_transaction_name_and_source(event, integration.transaction_style, req)
203
266
 
204
267
  with capture_internal_exceptions():
205
268
  FalconRequestExtractor(req).extract_into_event(event)
206
269
 
207
270
  return event
208
271
 
209
- return inner
272
+ return event_processor
@@ -0,0 +1,141 @@
1
+ import asyncio
2
+ from copy import deepcopy
3
+ from functools import wraps
4
+
5
+ import sentry_sdk
6
+ from sentry_sdk.integrations import DidNotEnable
7
+ from sentry_sdk.scope import should_send_default_pii
8
+ from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
9
+ from sentry_sdk.utils import transaction_from_function
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from typing import Any, Callable, Dict
15
+ from sentry_sdk._types import Event
16
+
17
+ try:
18
+ from sentry_sdk.integrations.starlette import (
19
+ StarletteIntegration,
20
+ StarletteRequestExtractor,
21
+ )
22
+ except DidNotEnable:
23
+ raise DidNotEnable("Starlette is not installed")
24
+
25
+ try:
26
+ import fastapi # type: ignore
27
+ except ImportError:
28
+ raise DidNotEnable("FastAPI is not installed")
29
+
30
+
31
+ _DEFAULT_TRANSACTION_NAME = "generic FastAPI request"
32
+
33
+
34
+ class FastApiIntegration(StarletteIntegration):
35
+ identifier = "fastapi"
36
+
37
+ @staticmethod
38
+ def setup_once():
39
+ # type: () -> None
40
+ patch_get_request_handler()
41
+
42
+
43
+ def _set_transaction_name_and_source(scope, transaction_style, request):
44
+ # type: (sentry_sdk.Scope, str, Any) -> None
45
+ name = ""
46
+
47
+ if transaction_style == "endpoint":
48
+ endpoint = request.scope.get("endpoint")
49
+ if endpoint:
50
+ name = transaction_from_function(endpoint) or ""
51
+
52
+ elif transaction_style == "url":
53
+ route = request.scope.get("route")
54
+ if route:
55
+ path = getattr(route, "path", None)
56
+ if path is not None:
57
+ name = path
58
+
59
+ if not name:
60
+ name = _DEFAULT_TRANSACTION_NAME
61
+ source = TransactionSource.ROUTE
62
+ else:
63
+ source = SOURCE_FOR_STYLE[transaction_style]
64
+
65
+ scope.set_transaction_name(name, source=source)
66
+
67
+
68
+ def patch_get_request_handler():
69
+ # type: () -> None
70
+ old_get_request_handler = fastapi.routing.get_request_handler
71
+
72
+ def _sentry_get_request_handler(*args, **kwargs):
73
+ # type: (*Any, **Any) -> Any
74
+ dependant = kwargs.get("dependant")
75
+ if (
76
+ dependant
77
+ and dependant.call is not None
78
+ and not asyncio.iscoroutinefunction(dependant.call)
79
+ ):
80
+ old_call = dependant.call
81
+
82
+ @wraps(old_call)
83
+ def _sentry_call(*args, **kwargs):
84
+ # type: (*Any, **Any) -> Any
85
+ current_scope = sentry_sdk.get_current_scope()
86
+ if current_scope.transaction is not None:
87
+ current_scope.transaction.update_active_thread()
88
+
89
+ sentry_scope = sentry_sdk.get_isolation_scope()
90
+ if sentry_scope.profile is not None:
91
+ sentry_scope.profile.update_active_thread_id()
92
+
93
+ return old_call(*args, **kwargs)
94
+
95
+ dependant.call = _sentry_call
96
+
97
+ old_app = old_get_request_handler(*args, **kwargs)
98
+
99
+ async def _sentry_app(*args, **kwargs):
100
+ # type: (*Any, **Any) -> Any
101
+ integration = sentry_sdk.get_client().get_integration(FastApiIntegration)
102
+ if integration is None:
103
+ return await old_app(*args, **kwargs)
104
+
105
+ request = args[0]
106
+
107
+ _set_transaction_name_and_source(
108
+ sentry_sdk.get_current_scope(), integration.transaction_style, request
109
+ )
110
+ sentry_scope = sentry_sdk.get_isolation_scope()
111
+ extractor = StarletteRequestExtractor(request)
112
+ info = await extractor.extract_request_info()
113
+
114
+ def _make_request_event_processor(req, integration):
115
+ # type: (Any, Any) -> Callable[[Event, Dict[str, Any]], Event]
116
+ def event_processor(event, hint):
117
+ # type: (Event, Dict[str, Any]) -> Event
118
+
119
+ # Extract information from request
120
+ request_info = event.get("request", {})
121
+ if info:
122
+ if "cookies" in info and should_send_default_pii():
123
+ request_info["cookies"] = info["cookies"]
124
+ if "data" in info:
125
+ request_info["data"] = info["data"]
126
+ event["request"] = deepcopy(request_info)
127
+
128
+ return event
129
+
130
+ return event_processor
131
+
132
+ sentry_scope._name = FastApiIntegration.identifier
133
+ sentry_scope.add_event_processor(
134
+ _make_request_event_processor(request, integration)
135
+ )
136
+
137
+ return await old_app(*args, **kwargs)
138
+
139
+ return _sentry_app
140
+
141
+ fastapi.routing.get_request_handler = _sentry_get_request_handler