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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -30
  2. sentry_sdk/_compat.py +74 -61
  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 +289 -0
  8. sentry_sdk/_types.py +338 -0
  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 +496 -80
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +1023 -103
  16. sentry_sdk/consts.py +1438 -66
  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 +15 -14
  22. sentry_sdk/envelope.py +369 -0
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +611 -280
  25. sentry_sdk/integrations/__init__.py +276 -49
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +180 -44
  28. sentry_sdk/integrations/aiohttp.py +291 -42
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +9 -8
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +341 -0
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +17 -10
  37. sentry_sdk/integrations/aws_lambda.py +377 -62
  38. sentry_sdk/integrations/beam.py +176 -0
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +221 -0
  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 +134 -0
  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 +48 -14
  49. sentry_sdk/integrations/django/__init__.py +584 -191
  50. sentry_sdk/integrations/django/asgi.py +245 -0
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +187 -0
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +79 -5
  55. sentry_sdk/integrations/django/transactions.py +49 -22
  56. sentry_sdk/integrations/django/views.py +96 -0
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +50 -13
  59. sentry_sdk/integrations/executing.py +67 -0
  60. sentry_sdk/integrations/falcon.py +272 -0
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +142 -88
  63. sentry_sdk/integrations/gcp.py +239 -0
  64. sentry_sdk/integrations/gnu_backtrace.py +99 -0
  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 +307 -96
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +14 -31
  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 +141 -0
  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 +112 -68
  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 +95 -37
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +294 -123
  145. sentry_sdk/integrations/serverless.py +48 -19
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/__init__.py +4 -0
  148. sentry_sdk/integrations/spark/spark_driver.py +316 -0
  149. sentry_sdk/integrations/spark/spark_worker.py +116 -0
  150. sentry_sdk/integrations/sqlalchemy.py +142 -0
  151. sentry_sdk/integrations/starlette.py +737 -0
  152. sentry_sdk/integrations/starlite.py +292 -0
  153. sentry_sdk/integrations/statsig.py +37 -0
  154. sentry_sdk/integrations/stdlib.py +235 -29
  155. sentry_sdk/integrations/strawberry.py +394 -0
  156. sentry_sdk/integrations/sys_exit.py +70 -0
  157. sentry_sdk/integrations/threading.py +158 -28
  158. sentry_sdk/integrations/tornado.py +84 -52
  159. sentry_sdk/integrations/trytond.py +50 -0
  160. sentry_sdk/integrations/typer.py +60 -0
  161. sentry_sdk/integrations/unleash.py +33 -0
  162. sentry_sdk/integrations/unraisablehook.py +53 -0
  163. sentry_sdk/integrations/wsgi.py +201 -119
  164. sentry_sdk/logger.py +96 -0
  165. sentry_sdk/metrics.py +81 -0
  166. sentry_sdk/monitor.py +120 -0
  167. sentry_sdk/profiler/__init__.py +49 -0
  168. sentry_sdk/profiler/continuous_profiler.py +730 -0
  169. sentry_sdk/profiler/transaction_profiler.py +839 -0
  170. sentry_sdk/profiler/utils.py +195 -0
  171. sentry_sdk/py.typed +0 -0
  172. sentry_sdk/scope.py +1713 -85
  173. sentry_sdk/scrubber.py +177 -0
  174. sentry_sdk/serializer.py +405 -0
  175. sentry_sdk/session.py +177 -0
  176. sentry_sdk/sessions.py +275 -0
  177. sentry_sdk/spotlight.py +242 -0
  178. sentry_sdk/tracing.py +1486 -0
  179. sentry_sdk/tracing_utils.py +1236 -0
  180. sentry_sdk/transport.py +806 -134
  181. sentry_sdk/types.py +52 -0
  182. sentry_sdk/utils.py +1625 -465
  183. sentry_sdk/worker.py +54 -25
  184. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  185. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  186. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  187. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  188. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  189. sentry_sdk/integrations/celery.py +0 -119
  190. sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.7.5.dist-info/METADATA +0 -36
  192. sentry_sdk-0.7.5.dist-info/RECORD +0 -39
  193. {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