sentry-sdk 0.7.5__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 -30
- sentry_sdk/_compat.py +74 -61
- 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 +289 -0
- sentry_sdk/_types.py +338 -0
- 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 +496 -80
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +1023 -103
- sentry_sdk/consts.py +1438 -66
- 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 +15 -14
- sentry_sdk/envelope.py +369 -0
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +611 -280
- sentry_sdk/integrations/__init__.py +276 -49
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +180 -44
- sentry_sdk/integrations/aiohttp.py +291 -42
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +9 -8
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +341 -0
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +17 -10
- sentry_sdk/integrations/aws_lambda.py +377 -62
- sentry_sdk/integrations/beam.py +176 -0
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +221 -0
- 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 +134 -0
- 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 +48 -14
- sentry_sdk/integrations/django/__init__.py +584 -191
- sentry_sdk/integrations/django/asgi.py +245 -0
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +187 -0
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +79 -5
- sentry_sdk/integrations/django/transactions.py +49 -22
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +50 -13
- sentry_sdk/integrations/executing.py +67 -0
- sentry_sdk/integrations/falcon.py +272 -0
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +142 -88
- sentry_sdk/integrations/gcp.py +239 -0
- sentry_sdk/integrations/gnu_backtrace.py +99 -0
- 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 +307 -96
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +14 -31
- 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 +141 -0
- 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 +112 -68
- 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 +95 -37
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +294 -123
- sentry_sdk/integrations/serverless.py +48 -19
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +316 -0
- sentry_sdk/integrations/spark/spark_worker.py +116 -0
- sentry_sdk/integrations/sqlalchemy.py +142 -0
- 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 +235 -29
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +158 -28
- sentry_sdk/integrations/tornado.py +84 -52
- sentry_sdk/integrations/trytond.py +50 -0
- 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 +201 -119
- 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/py.typed +0 -0
- sentry_sdk/scope.py +1713 -85
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +405 -0
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +275 -0
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1486 -0
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +806 -134
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1625 -465
- sentry_sdk/worker.py +54 -25
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.7.5.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/integrations/celery.py +0 -119
- sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
- sentry_sdk-0.7.5.dist-info/METADATA +0 -36
- sentry_sdk-0.7.5.dist-info/RECORD +0 -39
- {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
import sentry_sdk
|
|
6
|
+
from sentry_sdk.integrations import DidNotEnable, Integration
|
|
7
|
+
from sentry_sdk.integrations._wsgi_common import _filter_headers
|
|
8
|
+
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
|
|
9
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
10
|
+
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
|
11
|
+
from sentry_sdk.utils import (
|
|
12
|
+
capture_internal_exceptions,
|
|
13
|
+
ensure_integration_enabled,
|
|
14
|
+
event_from_exception,
|
|
15
|
+
)
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from typing import Any
|
|
20
|
+
from typing import Union
|
|
21
|
+
|
|
22
|
+
from sentry_sdk._types import Event, EventProcessor
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import quart_auth # type: ignore
|
|
26
|
+
except ImportError:
|
|
27
|
+
quart_auth = None
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from quart import ( # type: ignore
|
|
31
|
+
has_request_context,
|
|
32
|
+
has_websocket_context,
|
|
33
|
+
Request,
|
|
34
|
+
Quart,
|
|
35
|
+
request,
|
|
36
|
+
websocket,
|
|
37
|
+
)
|
|
38
|
+
from quart.signals import ( # type: ignore
|
|
39
|
+
got_background_exception,
|
|
40
|
+
got_request_exception,
|
|
41
|
+
got_websocket_exception,
|
|
42
|
+
request_started,
|
|
43
|
+
websocket_started,
|
|
44
|
+
)
|
|
45
|
+
except ImportError:
|
|
46
|
+
raise DidNotEnable("Quart is not installed")
|
|
47
|
+
else:
|
|
48
|
+
# Quart 0.19 is based on Flask and hence no longer has a Scaffold
|
|
49
|
+
try:
|
|
50
|
+
from quart.scaffold import Scaffold # type: ignore
|
|
51
|
+
except ImportError:
|
|
52
|
+
from flask.sansio.scaffold import Scaffold # type: ignore
|
|
53
|
+
|
|
54
|
+
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class QuartIntegration(Integration):
|
|
58
|
+
identifier = "quart"
|
|
59
|
+
origin = f"auto.http.{identifier}"
|
|
60
|
+
|
|
61
|
+
transaction_style = ""
|
|
62
|
+
|
|
63
|
+
def __init__(self, transaction_style="endpoint"):
|
|
64
|
+
# type: (str) -> None
|
|
65
|
+
if transaction_style not in TRANSACTION_STYLE_VALUES:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"Invalid value for transaction_style: %s (must be in %s)"
|
|
68
|
+
% (transaction_style, TRANSACTION_STYLE_VALUES)
|
|
69
|
+
)
|
|
70
|
+
self.transaction_style = transaction_style
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def setup_once():
|
|
74
|
+
# type: () -> None
|
|
75
|
+
|
|
76
|
+
request_started.connect(_request_websocket_started)
|
|
77
|
+
websocket_started.connect(_request_websocket_started)
|
|
78
|
+
got_background_exception.connect(_capture_exception)
|
|
79
|
+
got_request_exception.connect(_capture_exception)
|
|
80
|
+
got_websocket_exception.connect(_capture_exception)
|
|
81
|
+
|
|
82
|
+
patch_asgi_app()
|
|
83
|
+
patch_scaffold_route()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def patch_asgi_app():
|
|
87
|
+
# type: () -> None
|
|
88
|
+
old_app = Quart.__call__
|
|
89
|
+
|
|
90
|
+
async def sentry_patched_asgi_app(self, scope, receive, send):
|
|
91
|
+
# type: (Any, Any, Any, Any) -> Any
|
|
92
|
+
if sentry_sdk.get_client().get_integration(QuartIntegration) is None:
|
|
93
|
+
return await old_app(self, scope, receive, send)
|
|
94
|
+
|
|
95
|
+
middleware = SentryAsgiMiddleware(
|
|
96
|
+
lambda *a, **kw: old_app(self, *a, **kw),
|
|
97
|
+
span_origin=QuartIntegration.origin,
|
|
98
|
+
asgi_version=3,
|
|
99
|
+
)
|
|
100
|
+
return await middleware(scope, receive, send)
|
|
101
|
+
|
|
102
|
+
Quart.__call__ = sentry_patched_asgi_app
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def patch_scaffold_route():
|
|
106
|
+
# type: () -> None
|
|
107
|
+
old_route = Scaffold.route
|
|
108
|
+
|
|
109
|
+
def _sentry_route(*args, **kwargs):
|
|
110
|
+
# type: (*Any, **Any) -> Any
|
|
111
|
+
old_decorator = old_route(*args, **kwargs)
|
|
112
|
+
|
|
113
|
+
def decorator(old_func):
|
|
114
|
+
# type: (Any) -> Any
|
|
115
|
+
|
|
116
|
+
if inspect.isfunction(old_func) and not asyncio.iscoroutinefunction(
|
|
117
|
+
old_func
|
|
118
|
+
):
|
|
119
|
+
|
|
120
|
+
@wraps(old_func)
|
|
121
|
+
@ensure_integration_enabled(QuartIntegration, old_func)
|
|
122
|
+
def _sentry_func(*args, **kwargs):
|
|
123
|
+
# type: (*Any, **Any) -> Any
|
|
124
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
125
|
+
if current_scope.transaction is not None:
|
|
126
|
+
current_scope.transaction.update_active_thread()
|
|
127
|
+
|
|
128
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
129
|
+
if sentry_scope.profile is not None:
|
|
130
|
+
sentry_scope.profile.update_active_thread_id()
|
|
131
|
+
|
|
132
|
+
return old_func(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
return old_decorator(_sentry_func)
|
|
135
|
+
|
|
136
|
+
return old_decorator(old_func)
|
|
137
|
+
|
|
138
|
+
return decorator
|
|
139
|
+
|
|
140
|
+
Scaffold.route = _sentry_route
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _set_transaction_name_and_source(scope, transaction_style, request):
|
|
144
|
+
# type: (sentry_sdk.Scope, str, Request) -> None
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
name_for_style = {
|
|
148
|
+
"url": request.url_rule.rule,
|
|
149
|
+
"endpoint": request.url_rule.endpoint,
|
|
150
|
+
}
|
|
151
|
+
scope.set_transaction_name(
|
|
152
|
+
name_for_style[transaction_style],
|
|
153
|
+
source=SOURCE_FOR_STYLE[transaction_style],
|
|
154
|
+
)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def _request_websocket_started(app, **kwargs):
|
|
160
|
+
# type: (Quart, **Any) -> None
|
|
161
|
+
integration = sentry_sdk.get_client().get_integration(QuartIntegration)
|
|
162
|
+
if integration is None:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
if has_request_context():
|
|
166
|
+
request_websocket = request._get_current_object()
|
|
167
|
+
if has_websocket_context():
|
|
168
|
+
request_websocket = websocket._get_current_object()
|
|
169
|
+
|
|
170
|
+
# Set the transaction name here, but rely on ASGI middleware
|
|
171
|
+
# to actually start the transaction
|
|
172
|
+
_set_transaction_name_and_source(
|
|
173
|
+
sentry_sdk.get_current_scope(), integration.transaction_style, request_websocket
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
scope = sentry_sdk.get_isolation_scope()
|
|
177
|
+
evt_processor = _make_request_event_processor(app, request_websocket, integration)
|
|
178
|
+
scope.add_event_processor(evt_processor)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _make_request_event_processor(app, request, integration):
|
|
182
|
+
# type: (Quart, Request, QuartIntegration) -> EventProcessor
|
|
183
|
+
def inner(event, hint):
|
|
184
|
+
# type: (Event, dict[str, Any]) -> Event
|
|
185
|
+
# if the request is gone we are fine not logging the data from
|
|
186
|
+
# it. This might happen if the processor is pushed away to
|
|
187
|
+
# another thread.
|
|
188
|
+
if request is None:
|
|
189
|
+
return event
|
|
190
|
+
|
|
191
|
+
with capture_internal_exceptions():
|
|
192
|
+
# TODO: Figure out what to do with request body. Methods on request
|
|
193
|
+
# are async, but event processors are not.
|
|
194
|
+
|
|
195
|
+
request_info = event.setdefault("request", {})
|
|
196
|
+
request_info["url"] = request.url
|
|
197
|
+
request_info["query_string"] = request.query_string
|
|
198
|
+
request_info["method"] = request.method
|
|
199
|
+
request_info["headers"] = _filter_headers(dict(request.headers))
|
|
200
|
+
|
|
201
|
+
if should_send_default_pii():
|
|
202
|
+
request_info["env"] = {"REMOTE_ADDR": request.access_route[0]}
|
|
203
|
+
_add_user_to_event(event)
|
|
204
|
+
|
|
205
|
+
return event
|
|
206
|
+
|
|
207
|
+
return inner
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def _capture_exception(sender, exception, **kwargs):
|
|
211
|
+
# type: (Quart, Union[ValueError, BaseException], **Any) -> None
|
|
212
|
+
integration = sentry_sdk.get_client().get_integration(QuartIntegration)
|
|
213
|
+
if integration is None:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
event, hint = event_from_exception(
|
|
217
|
+
exception,
|
|
218
|
+
client_options=sentry_sdk.get_client().options,
|
|
219
|
+
mechanism={"type": "quart", "handled": False},
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _add_user_to_event(event):
|
|
226
|
+
# type: (Event) -> None
|
|
227
|
+
if quart_auth is None:
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
user = quart_auth.current_user
|
|
231
|
+
if user is None:
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
with capture_internal_exceptions():
|
|
235
|
+
user_info = event.setdefault("user", {})
|
|
236
|
+
|
|
237
|
+
user_info["id"] = quart_auth.current_user._auth_id
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import functools
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import sentry_sdk
|
|
6
|
+
from sentry_sdk.consts import OP, SPANSTATUS
|
|
7
|
+
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
|
|
8
|
+
from sentry_sdk.tracing import TransactionSource
|
|
9
|
+
from sentry_sdk.utils import (
|
|
10
|
+
event_from_exception,
|
|
11
|
+
logger,
|
|
12
|
+
package_version,
|
|
13
|
+
qualname_from_function,
|
|
14
|
+
reraise,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import ray # type: ignore[import-not-found]
|
|
19
|
+
except ImportError:
|
|
20
|
+
raise DidNotEnable("Ray not installed.")
|
|
21
|
+
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
from sentry_sdk.utils import ExcInfo
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _check_sentry_initialized():
|
|
31
|
+
# type: () -> None
|
|
32
|
+
if sentry_sdk.get_client().is_active():
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
logger.debug(
|
|
36
|
+
"[Tracing] Sentry not initialized in ray cluster worker, performance data will be discarded."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _patch_ray_remote():
|
|
41
|
+
# type: () -> None
|
|
42
|
+
old_remote = ray.remote
|
|
43
|
+
|
|
44
|
+
@functools.wraps(old_remote)
|
|
45
|
+
def new_remote(f=None, *args, **kwargs):
|
|
46
|
+
# type: (Optional[Callable[..., Any]], *Any, **Any) -> Callable[..., Any]
|
|
47
|
+
|
|
48
|
+
if inspect.isclass(f):
|
|
49
|
+
# Ray Actors
|
|
50
|
+
# (https://docs.ray.io/en/latest/ray-core/actors.html)
|
|
51
|
+
# are not supported
|
|
52
|
+
# (Only Ray Tasks are supported)
|
|
53
|
+
return old_remote(f, *args, **kwargs)
|
|
54
|
+
|
|
55
|
+
def wrapper(user_f):
|
|
56
|
+
# type: (Callable[..., Any]) -> Any
|
|
57
|
+
@functools.wraps(user_f)
|
|
58
|
+
def new_func(*f_args, _sentry_tracing=None, **f_kwargs):
|
|
59
|
+
# type: (Any, Optional[dict[str, Any]], Any) -> Any
|
|
60
|
+
_check_sentry_initialized()
|
|
61
|
+
|
|
62
|
+
transaction = sentry_sdk.continue_trace(
|
|
63
|
+
_sentry_tracing or {},
|
|
64
|
+
op=OP.QUEUE_TASK_RAY,
|
|
65
|
+
name=qualname_from_function(user_f),
|
|
66
|
+
origin=RayIntegration.origin,
|
|
67
|
+
source=TransactionSource.TASK,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
with sentry_sdk.start_transaction(transaction) as transaction:
|
|
71
|
+
try:
|
|
72
|
+
result = user_f(*f_args, **f_kwargs)
|
|
73
|
+
transaction.set_status(SPANSTATUS.OK)
|
|
74
|
+
except Exception:
|
|
75
|
+
transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
|
|
76
|
+
exc_info = sys.exc_info()
|
|
77
|
+
_capture_exception(exc_info)
|
|
78
|
+
reraise(*exc_info)
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
# Patching new_func signature to add the _sentry_tracing parameter to it
|
|
83
|
+
# Ray later inspects the signature and finds the unexpected parameter otherwise
|
|
84
|
+
signature = inspect.signature(new_func)
|
|
85
|
+
params = list(signature.parameters.values())
|
|
86
|
+
params.append(
|
|
87
|
+
inspect.Parameter(
|
|
88
|
+
"_sentry_tracing",
|
|
89
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
90
|
+
default=None,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
new_func.__signature__ = signature.replace(parameters=params) # type: ignore[attr-defined]
|
|
94
|
+
|
|
95
|
+
if f:
|
|
96
|
+
rv = old_remote(new_func)
|
|
97
|
+
else:
|
|
98
|
+
rv = old_remote(*args, **kwargs)(new_func)
|
|
99
|
+
old_remote_method = rv.remote
|
|
100
|
+
|
|
101
|
+
def _remote_method_with_header_propagation(*args, **kwargs):
|
|
102
|
+
# type: (*Any, **Any) -> Any
|
|
103
|
+
"""
|
|
104
|
+
Ray Client
|
|
105
|
+
"""
|
|
106
|
+
with sentry_sdk.start_span(
|
|
107
|
+
op=OP.QUEUE_SUBMIT_RAY,
|
|
108
|
+
name=qualname_from_function(user_f),
|
|
109
|
+
origin=RayIntegration.origin,
|
|
110
|
+
) as span:
|
|
111
|
+
tracing = {
|
|
112
|
+
k: v
|
|
113
|
+
for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers()
|
|
114
|
+
}
|
|
115
|
+
try:
|
|
116
|
+
result = old_remote_method(
|
|
117
|
+
*args, **kwargs, _sentry_tracing=tracing
|
|
118
|
+
)
|
|
119
|
+
span.set_status(SPANSTATUS.OK)
|
|
120
|
+
except Exception:
|
|
121
|
+
span.set_status(SPANSTATUS.INTERNAL_ERROR)
|
|
122
|
+
exc_info = sys.exc_info()
|
|
123
|
+
_capture_exception(exc_info)
|
|
124
|
+
reraise(*exc_info)
|
|
125
|
+
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
rv.remote = _remote_method_with_header_propagation
|
|
129
|
+
|
|
130
|
+
return rv
|
|
131
|
+
|
|
132
|
+
if f is not None:
|
|
133
|
+
return wrapper(f)
|
|
134
|
+
else:
|
|
135
|
+
return wrapper
|
|
136
|
+
|
|
137
|
+
ray.remote = new_remote
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _capture_exception(exc_info, **kwargs):
|
|
141
|
+
# type: (ExcInfo, **Any) -> None
|
|
142
|
+
client = sentry_sdk.get_client()
|
|
143
|
+
|
|
144
|
+
event, hint = event_from_exception(
|
|
145
|
+
exc_info,
|
|
146
|
+
client_options=client.options,
|
|
147
|
+
mechanism={
|
|
148
|
+
"handled": False,
|
|
149
|
+
"type": RayIntegration.identifier,
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class RayIntegration(Integration):
|
|
156
|
+
identifier = "ray"
|
|
157
|
+
origin = f"auto.queue.{identifier}"
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def setup_once():
|
|
161
|
+
# type: () -> None
|
|
162
|
+
version = package_version("ray")
|
|
163
|
+
_check_minimum_version(RayIntegration, version)
|
|
164
|
+
|
|
165
|
+
_patch_ray_remote()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
4
|
+
from sentry_sdk.integrations.redis.consts import _DEFAULT_MAX_DATA_SIZE
|
|
5
|
+
from sentry_sdk.integrations.redis.rb import _patch_rb
|
|
6
|
+
from sentry_sdk.integrations.redis.redis import _patch_redis
|
|
7
|
+
from sentry_sdk.integrations.redis.redis_cluster import _patch_redis_cluster
|
|
8
|
+
from sentry_sdk.integrations.redis.redis_py_cluster_legacy import _patch_rediscluster
|
|
9
|
+
from sentry_sdk.utils import logger
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RedisIntegration(Integration):
|
|
18
|
+
identifier = "redis"
|
|
19
|
+
|
|
20
|
+
def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
|
|
21
|
+
# type: (Optional[int], Optional[list[str]]) -> None
|
|
22
|
+
self.max_data_size = max_data_size
|
|
23
|
+
self.cache_prefixes = cache_prefixes if cache_prefixes is not None else []
|
|
24
|
+
|
|
25
|
+
if max_data_size is not None:
|
|
26
|
+
warnings.warn(
|
|
27
|
+
"The `max_data_size` parameter of `RedisIntegration` is "
|
|
28
|
+
"deprecated and will be removed in version 3.0 of sentry-sdk.",
|
|
29
|
+
DeprecationWarning,
|
|
30
|
+
stacklevel=2,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def setup_once():
|
|
35
|
+
# type: () -> None
|
|
36
|
+
try:
|
|
37
|
+
from redis import StrictRedis, client
|
|
38
|
+
except ImportError:
|
|
39
|
+
raise DidNotEnable("Redis client not installed")
|
|
40
|
+
|
|
41
|
+
_patch_redis(StrictRedis, client)
|
|
42
|
+
_patch_redis_cluster()
|
|
43
|
+
_patch_rb()
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
_patch_rediscluster()
|
|
47
|
+
except Exception:
|
|
48
|
+
logger.exception("Error occurred while patching `rediscluster` library")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
from sentry_sdk.consts import OP
|
|
3
|
+
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
|
|
4
|
+
from sentry_sdk.integrations.redis.modules.caches import (
|
|
5
|
+
_compile_cache_span_properties,
|
|
6
|
+
_set_cache_data,
|
|
7
|
+
)
|
|
8
|
+
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
|
|
9
|
+
from sentry_sdk.integrations.redis.utils import (
|
|
10
|
+
_set_client_data,
|
|
11
|
+
_set_pipeline_data,
|
|
12
|
+
)
|
|
13
|
+
from sentry_sdk.tracing import Span
|
|
14
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from typing import Any, Union
|
|
21
|
+
from redis.asyncio.client import Pipeline, StrictRedis
|
|
22
|
+
from redis.asyncio.cluster import ClusterPipeline, RedisCluster
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def patch_redis_async_pipeline(
|
|
26
|
+
pipeline_cls, is_cluster, get_command_args_fn, set_db_data_fn
|
|
27
|
+
):
|
|
28
|
+
# type: (Union[type[Pipeline[Any]], type[ClusterPipeline[Any]]], bool, Any, Callable[[Span, Any], None]) -> None
|
|
29
|
+
old_execute = pipeline_cls.execute
|
|
30
|
+
|
|
31
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
32
|
+
|
|
33
|
+
async def _sentry_execute(self, *args, **kwargs):
|
|
34
|
+
# type: (Any, *Any, **Any) -> Any
|
|
35
|
+
if sentry_sdk.get_client().get_integration(RedisIntegration) is None:
|
|
36
|
+
return await old_execute(self, *args, **kwargs)
|
|
37
|
+
|
|
38
|
+
with sentry_sdk.start_span(
|
|
39
|
+
op=OP.DB_REDIS,
|
|
40
|
+
name="redis.pipeline.execute",
|
|
41
|
+
origin=SPAN_ORIGIN,
|
|
42
|
+
) as span:
|
|
43
|
+
with capture_internal_exceptions():
|
|
44
|
+
try:
|
|
45
|
+
command_seq = self._execution_strategy._command_queue
|
|
46
|
+
except AttributeError:
|
|
47
|
+
if is_cluster:
|
|
48
|
+
command_seq = self._command_stack
|
|
49
|
+
else:
|
|
50
|
+
command_seq = self.command_stack
|
|
51
|
+
|
|
52
|
+
set_db_data_fn(span, self)
|
|
53
|
+
_set_pipeline_data(
|
|
54
|
+
span,
|
|
55
|
+
is_cluster,
|
|
56
|
+
get_command_args_fn,
|
|
57
|
+
False if is_cluster else self.is_transaction,
|
|
58
|
+
command_seq,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return await old_execute(self, *args, **kwargs)
|
|
62
|
+
|
|
63
|
+
pipeline_cls.execute = _sentry_execute # type: ignore
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def patch_redis_async_client(cls, is_cluster, set_db_data_fn):
|
|
67
|
+
# type: (Union[type[StrictRedis[Any]], type[RedisCluster[Any]]], bool, Callable[[Span, Any], None]) -> None
|
|
68
|
+
old_execute_command = cls.execute_command
|
|
69
|
+
|
|
70
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
71
|
+
|
|
72
|
+
async def _sentry_execute_command(self, name, *args, **kwargs):
|
|
73
|
+
# type: (Any, str, *Any, **Any) -> Any
|
|
74
|
+
integration = sentry_sdk.get_client().get_integration(RedisIntegration)
|
|
75
|
+
if integration is None:
|
|
76
|
+
return await old_execute_command(self, name, *args, **kwargs)
|
|
77
|
+
|
|
78
|
+
cache_properties = _compile_cache_span_properties(
|
|
79
|
+
name,
|
|
80
|
+
args,
|
|
81
|
+
kwargs,
|
|
82
|
+
integration,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
cache_span = None
|
|
86
|
+
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
|
|
87
|
+
cache_span = sentry_sdk.start_span(
|
|
88
|
+
op=cache_properties["op"],
|
|
89
|
+
name=cache_properties["description"],
|
|
90
|
+
origin=SPAN_ORIGIN,
|
|
91
|
+
)
|
|
92
|
+
cache_span.__enter__()
|
|
93
|
+
|
|
94
|
+
db_properties = _compile_db_span_properties(integration, name, args)
|
|
95
|
+
|
|
96
|
+
db_span = sentry_sdk.start_span(
|
|
97
|
+
op=db_properties["op"],
|
|
98
|
+
name=db_properties["description"],
|
|
99
|
+
origin=SPAN_ORIGIN,
|
|
100
|
+
)
|
|
101
|
+
db_span.__enter__()
|
|
102
|
+
|
|
103
|
+
set_db_data_fn(db_span, self)
|
|
104
|
+
_set_client_data(db_span, is_cluster, name, *args)
|
|
105
|
+
|
|
106
|
+
value = await old_execute_command(self, name, *args, **kwargs)
|
|
107
|
+
|
|
108
|
+
db_span.__exit__(None, None, None)
|
|
109
|
+
|
|
110
|
+
if cache_span:
|
|
111
|
+
_set_cache_data(cache_span, self, cache_properties, value)
|
|
112
|
+
cache_span.__exit__(None, None, None)
|
|
113
|
+
|
|
114
|
+
return value
|
|
115
|
+
|
|
116
|
+
cls.execute_command = _sentry_execute_command # type: ignore
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
from sentry_sdk.consts import OP
|
|
3
|
+
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
|
|
4
|
+
from sentry_sdk.integrations.redis.modules.caches import (
|
|
5
|
+
_compile_cache_span_properties,
|
|
6
|
+
_set_cache_data,
|
|
7
|
+
)
|
|
8
|
+
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
|
|
9
|
+
from sentry_sdk.integrations.redis.utils import (
|
|
10
|
+
_set_client_data,
|
|
11
|
+
_set_pipeline_data,
|
|
12
|
+
)
|
|
13
|
+
from sentry_sdk.tracing import Span
|
|
14
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def patch_redis_pipeline(
|
|
24
|
+
pipeline_cls,
|
|
25
|
+
is_cluster,
|
|
26
|
+
get_command_args_fn,
|
|
27
|
+
set_db_data_fn,
|
|
28
|
+
):
|
|
29
|
+
# type: (Any, bool, Any, Callable[[Span, Any], None]) -> None
|
|
30
|
+
old_execute = pipeline_cls.execute
|
|
31
|
+
|
|
32
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
33
|
+
|
|
34
|
+
def sentry_patched_execute(self, *args, **kwargs):
|
|
35
|
+
# type: (Any, *Any, **Any) -> Any
|
|
36
|
+
if sentry_sdk.get_client().get_integration(RedisIntegration) is None:
|
|
37
|
+
return old_execute(self, *args, **kwargs)
|
|
38
|
+
|
|
39
|
+
with sentry_sdk.start_span(
|
|
40
|
+
op=OP.DB_REDIS,
|
|
41
|
+
name="redis.pipeline.execute",
|
|
42
|
+
origin=SPAN_ORIGIN,
|
|
43
|
+
) as span:
|
|
44
|
+
with capture_internal_exceptions():
|
|
45
|
+
command_seq = None
|
|
46
|
+
try:
|
|
47
|
+
command_seq = self._execution_strategy.command_queue
|
|
48
|
+
except AttributeError:
|
|
49
|
+
command_seq = self.command_stack
|
|
50
|
+
|
|
51
|
+
set_db_data_fn(span, self)
|
|
52
|
+
_set_pipeline_data(
|
|
53
|
+
span,
|
|
54
|
+
is_cluster,
|
|
55
|
+
get_command_args_fn,
|
|
56
|
+
False if is_cluster else self.transaction,
|
|
57
|
+
command_seq,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return old_execute(self, *args, **kwargs)
|
|
61
|
+
|
|
62
|
+
pipeline_cls.execute = sentry_patched_execute
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def patch_redis_client(cls, is_cluster, set_db_data_fn):
|
|
66
|
+
# type: (Any, bool, Callable[[Span, Any], None]) -> None
|
|
67
|
+
"""
|
|
68
|
+
This function can be used to instrument custom redis client classes or
|
|
69
|
+
subclasses.
|
|
70
|
+
"""
|
|
71
|
+
old_execute_command = cls.execute_command
|
|
72
|
+
|
|
73
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
74
|
+
|
|
75
|
+
def sentry_patched_execute_command(self, name, *args, **kwargs):
|
|
76
|
+
# type: (Any, str, *Any, **Any) -> Any
|
|
77
|
+
integration = sentry_sdk.get_client().get_integration(RedisIntegration)
|
|
78
|
+
if integration is None:
|
|
79
|
+
return old_execute_command(self, name, *args, **kwargs)
|
|
80
|
+
|
|
81
|
+
cache_properties = _compile_cache_span_properties(
|
|
82
|
+
name,
|
|
83
|
+
args,
|
|
84
|
+
kwargs,
|
|
85
|
+
integration,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
cache_span = None
|
|
89
|
+
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
|
|
90
|
+
cache_span = sentry_sdk.start_span(
|
|
91
|
+
op=cache_properties["op"],
|
|
92
|
+
name=cache_properties["description"],
|
|
93
|
+
origin=SPAN_ORIGIN,
|
|
94
|
+
)
|
|
95
|
+
cache_span.__enter__()
|
|
96
|
+
|
|
97
|
+
db_properties = _compile_db_span_properties(integration, name, args)
|
|
98
|
+
|
|
99
|
+
db_span = sentry_sdk.start_span(
|
|
100
|
+
op=db_properties["op"],
|
|
101
|
+
name=db_properties["description"],
|
|
102
|
+
origin=SPAN_ORIGIN,
|
|
103
|
+
)
|
|
104
|
+
db_span.__enter__()
|
|
105
|
+
|
|
106
|
+
set_db_data_fn(db_span, self)
|
|
107
|
+
_set_client_data(db_span, is_cluster, name, *args)
|
|
108
|
+
|
|
109
|
+
value = old_execute_command(self, name, *args, **kwargs)
|
|
110
|
+
|
|
111
|
+
db_span.__exit__(None, None, None)
|
|
112
|
+
|
|
113
|
+
if cache_span:
|
|
114
|
+
_set_cache_data(cache_span, self, cache_properties, value)
|
|
115
|
+
cache_span.__exit__(None, None, None)
|
|
116
|
+
|
|
117
|
+
return value
|
|
118
|
+
|
|
119
|
+
cls.execute_command = sentry_patched_execute_command
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
SPAN_ORIGIN = "auto.db.redis"
|
|
2
|
+
|
|
3
|
+
_SINGLE_KEY_COMMANDS = frozenset(
|
|
4
|
+
["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"],
|
|
5
|
+
)
|
|
6
|
+
_MULTI_KEY_COMMANDS = frozenset(
|
|
7
|
+
[
|
|
8
|
+
"del",
|
|
9
|
+
"touch",
|
|
10
|
+
"unlink",
|
|
11
|
+
"mget",
|
|
12
|
+
],
|
|
13
|
+
)
|
|
14
|
+
_COMMANDS_INCLUDING_SENSITIVE_DATA = [
|
|
15
|
+
"auth",
|
|
16
|
+
]
|
|
17
|
+
_MAX_NUM_ARGS = 10 # Trim argument lists to this many values
|
|
18
|
+
_MAX_NUM_COMMANDS = 10 # Trim command lists to this many values
|
|
19
|
+
_DEFAULT_MAX_DATA_SIZE = None
|
|
File without changes
|