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.
- sentry_sdk/__init__.py +48 -6
- sentry_sdk/_compat.py +64 -56
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +81 -19
- sentry_sdk/_types.py +311 -11
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +35 -28
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +20 -11
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +71 -60
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +126 -125
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
10
14
|
|
|
11
|
-
if
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 =
|
|
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
|
-
|
|
110
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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=
|
|
193
|
+
client_options=sentry_sdk.get_client().options,
|
|
163
194
|
mechanism={"type": "falcon", "handled": False},
|
|
164
195
|
)
|
|
165
|
-
|
|
196
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
166
197
|
|
|
167
198
|
return was_handled
|
|
168
199
|
|
|
169
|
-
|
|
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 =
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
|
190
|
-
# type: (
|
|
191
|
-
|
|
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
|
|
198
|
-
# type: (
|
|
199
|
-
|
|
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
|
|
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
|