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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,394 @@
1
+ import functools
2
+ import hashlib
3
+ import warnings
4
+ from inspect import isawaitable
5
+
6
+ import sentry_sdk
7
+ from sentry_sdk.consts import OP
8
+ from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
9
+ from sentry_sdk.integrations.logging import ignore_logger
10
+ from sentry_sdk.scope import should_send_default_pii
11
+ from sentry_sdk.tracing import TransactionSource
12
+ from sentry_sdk.utils import (
13
+ capture_internal_exceptions,
14
+ ensure_integration_enabled,
15
+ event_from_exception,
16
+ logger,
17
+ package_version,
18
+ _get_installed_modules,
19
+ )
20
+
21
+ try:
22
+ from functools import cached_property
23
+ except ImportError:
24
+ # The strawberry integration requires Python 3.8+. functools.cached_property
25
+ # was added in 3.8, so this check is technically not needed, but since this
26
+ # is an auto-enabling integration, we might get to executing this import in
27
+ # lower Python versions, so we need to deal with it.
28
+ raise DidNotEnable("strawberry-graphql integration requires Python 3.8 or newer")
29
+
30
+ try:
31
+ from strawberry import Schema
32
+ from strawberry.extensions import SchemaExtension
33
+ from strawberry.extensions.tracing.utils import (
34
+ should_skip_tracing as strawberry_should_skip_tracing,
35
+ )
36
+ from strawberry.http import async_base_view, sync_base_view
37
+ except ImportError:
38
+ raise DidNotEnable("strawberry-graphql is not installed")
39
+
40
+ try:
41
+ from strawberry.extensions.tracing import (
42
+ SentryTracingExtension as StrawberrySentryAsyncExtension,
43
+ SentryTracingExtensionSync as StrawberrySentrySyncExtension,
44
+ )
45
+ except ImportError:
46
+ StrawberrySentryAsyncExtension = None
47
+ StrawberrySentrySyncExtension = None
48
+
49
+ from typing import TYPE_CHECKING
50
+
51
+ if TYPE_CHECKING:
52
+ from typing import Any, Callable, Generator, List, Optional
53
+ from graphql import GraphQLError, GraphQLResolveInfo
54
+ from strawberry.http import GraphQLHTTPResponse
55
+ from strawberry.types import ExecutionContext
56
+ from sentry_sdk._types import Event, EventProcessor
57
+
58
+
59
+ ignore_logger("strawberry.execution")
60
+
61
+
62
+ class StrawberryIntegration(Integration):
63
+ identifier = "strawberry"
64
+ origin = f"auto.graphql.{identifier}"
65
+
66
+ def __init__(self, async_execution=None):
67
+ # type: (Optional[bool]) -> None
68
+ if async_execution not in (None, False, True):
69
+ raise ValueError(
70
+ 'Invalid value for async_execution: "{}" (must be bool)'.format(
71
+ async_execution
72
+ )
73
+ )
74
+ self.async_execution = async_execution
75
+
76
+ @staticmethod
77
+ def setup_once():
78
+ # type: () -> None
79
+ version = package_version("strawberry-graphql")
80
+ _check_minimum_version(StrawberryIntegration, version, "strawberry-graphql")
81
+
82
+ _patch_schema_init()
83
+ _patch_views()
84
+
85
+
86
+ def _patch_schema_init():
87
+ # type: () -> None
88
+ old_schema_init = Schema.__init__
89
+
90
+ @functools.wraps(old_schema_init)
91
+ def _sentry_patched_schema_init(self, *args, **kwargs):
92
+ # type: (Schema, Any, Any) -> None
93
+ integration = sentry_sdk.get_client().get_integration(StrawberryIntegration)
94
+ if integration is None:
95
+ return old_schema_init(self, *args, **kwargs)
96
+
97
+ extensions = kwargs.get("extensions") or []
98
+
99
+ should_use_async_extension = None # type: Optional[bool]
100
+ if integration.async_execution is not None:
101
+ should_use_async_extension = integration.async_execution
102
+ else:
103
+ # try to figure it out ourselves
104
+ should_use_async_extension = _guess_if_using_async(extensions)
105
+
106
+ if should_use_async_extension is None:
107
+ warnings.warn(
108
+ "Assuming strawberry is running sync. If not, initialize the integration as StrawberryIntegration(async_execution=True).",
109
+ stacklevel=2,
110
+ )
111
+ should_use_async_extension = False
112
+
113
+ # remove the built in strawberry sentry extension, if present
114
+ extensions = [
115
+ extension
116
+ for extension in extensions
117
+ if extension
118
+ not in (StrawberrySentryAsyncExtension, StrawberrySentrySyncExtension)
119
+ ]
120
+
121
+ # add our extension
122
+ extensions.append(
123
+ SentryAsyncExtension if should_use_async_extension else SentrySyncExtension
124
+ )
125
+
126
+ kwargs["extensions"] = extensions
127
+
128
+ return old_schema_init(self, *args, **kwargs)
129
+
130
+ Schema.__init__ = _sentry_patched_schema_init # type: ignore[method-assign]
131
+
132
+
133
+ class SentryAsyncExtension(SchemaExtension):
134
+ def __init__(
135
+ self,
136
+ *,
137
+ execution_context=None,
138
+ ):
139
+ # type: (Any, Optional[ExecutionContext]) -> None
140
+ if execution_context:
141
+ self.execution_context = execution_context
142
+
143
+ @cached_property
144
+ def _resource_name(self):
145
+ # type: () -> str
146
+ query_hash = self.hash_query(self.execution_context.query) # type: ignore
147
+
148
+ if self.execution_context.operation_name:
149
+ return "{}:{}".format(self.execution_context.operation_name, query_hash)
150
+
151
+ return query_hash
152
+
153
+ def hash_query(self, query):
154
+ # type: (str) -> str
155
+ return hashlib.md5(query.encode("utf-8")).hexdigest()
156
+
157
+ def on_operation(self):
158
+ # type: () -> Generator[None, None, None]
159
+ self._operation_name = self.execution_context.operation_name
160
+
161
+ operation_type = "query"
162
+ op = OP.GRAPHQL_QUERY
163
+
164
+ if self.execution_context.query is None:
165
+ self.execution_context.query = ""
166
+
167
+ if self.execution_context.query.strip().startswith("mutation"):
168
+ operation_type = "mutation"
169
+ op = OP.GRAPHQL_MUTATION
170
+ elif self.execution_context.query.strip().startswith("subscription"):
171
+ operation_type = "subscription"
172
+ op = OP.GRAPHQL_SUBSCRIPTION
173
+
174
+ description = operation_type
175
+ if self._operation_name:
176
+ description += " {}".format(self._operation_name)
177
+
178
+ sentry_sdk.add_breadcrumb(
179
+ category="graphql.operation",
180
+ data={
181
+ "operation_name": self._operation_name,
182
+ "operation_type": operation_type,
183
+ },
184
+ )
185
+
186
+ scope = sentry_sdk.get_isolation_scope()
187
+ event_processor = _make_request_event_processor(self.execution_context)
188
+ scope.add_event_processor(event_processor)
189
+
190
+ span = sentry_sdk.get_current_span()
191
+ if span:
192
+ self.graphql_span = span.start_child(
193
+ op=op,
194
+ name=description,
195
+ origin=StrawberryIntegration.origin,
196
+ )
197
+ else:
198
+ self.graphql_span = sentry_sdk.start_span(
199
+ op=op,
200
+ name=description,
201
+ origin=StrawberryIntegration.origin,
202
+ )
203
+
204
+ self.graphql_span.set_data("graphql.operation.type", operation_type)
205
+ self.graphql_span.set_data("graphql.operation.name", self._operation_name)
206
+ self.graphql_span.set_data("graphql.document", self.execution_context.query)
207
+ self.graphql_span.set_data("graphql.resource_name", self._resource_name)
208
+
209
+ yield
210
+
211
+ transaction = self.graphql_span.containing_transaction
212
+ if transaction and self.execution_context.operation_name:
213
+ transaction.name = self.execution_context.operation_name
214
+ transaction.source = TransactionSource.COMPONENT
215
+ transaction.op = op
216
+
217
+ self.graphql_span.finish()
218
+
219
+ def on_validate(self):
220
+ # type: () -> Generator[None, None, None]
221
+ self.validation_span = self.graphql_span.start_child(
222
+ op=OP.GRAPHQL_VALIDATE,
223
+ name="validation",
224
+ origin=StrawberryIntegration.origin,
225
+ )
226
+
227
+ yield
228
+
229
+ self.validation_span.finish()
230
+
231
+ def on_parse(self):
232
+ # type: () -> Generator[None, None, None]
233
+ self.parsing_span = self.graphql_span.start_child(
234
+ op=OP.GRAPHQL_PARSE,
235
+ name="parsing",
236
+ origin=StrawberryIntegration.origin,
237
+ )
238
+
239
+ yield
240
+
241
+ self.parsing_span.finish()
242
+
243
+ def should_skip_tracing(self, _next, info):
244
+ # type: (Callable[[Any, GraphQLResolveInfo, Any, Any], Any], GraphQLResolveInfo) -> bool
245
+ return strawberry_should_skip_tracing(_next, info)
246
+
247
+ async def _resolve(self, _next, root, info, *args, **kwargs):
248
+ # type: (Callable[[Any, GraphQLResolveInfo, Any, Any], Any], Any, GraphQLResolveInfo, str, Any) -> Any
249
+ result = _next(root, info, *args, **kwargs)
250
+
251
+ if isawaitable(result):
252
+ result = await result
253
+
254
+ return result
255
+
256
+ async def resolve(self, _next, root, info, *args, **kwargs):
257
+ # type: (Callable[[Any, GraphQLResolveInfo, Any, Any], Any], Any, GraphQLResolveInfo, str, Any) -> Any
258
+ if self.should_skip_tracing(_next, info):
259
+ return await self._resolve(_next, root, info, *args, **kwargs)
260
+
261
+ field_path = "{}.{}".format(info.parent_type, info.field_name)
262
+
263
+ with self.graphql_span.start_child(
264
+ op=OP.GRAPHQL_RESOLVE,
265
+ name="resolving {}".format(field_path),
266
+ origin=StrawberryIntegration.origin,
267
+ ) as span:
268
+ span.set_data("graphql.field_name", info.field_name)
269
+ span.set_data("graphql.parent_type", info.parent_type.name)
270
+ span.set_data("graphql.field_path", field_path)
271
+ span.set_data("graphql.path", ".".join(map(str, info.path.as_list())))
272
+
273
+ return await self._resolve(_next, root, info, *args, **kwargs)
274
+
275
+
276
+ class SentrySyncExtension(SentryAsyncExtension):
277
+ def resolve(self, _next, root, info, *args, **kwargs):
278
+ # type: (Callable[[Any, Any, Any, Any], Any], Any, GraphQLResolveInfo, str, Any) -> Any
279
+ if self.should_skip_tracing(_next, info):
280
+ return _next(root, info, *args, **kwargs)
281
+
282
+ field_path = "{}.{}".format(info.parent_type, info.field_name)
283
+
284
+ with self.graphql_span.start_child(
285
+ op=OP.GRAPHQL_RESOLVE,
286
+ name="resolving {}".format(field_path),
287
+ origin=StrawberryIntegration.origin,
288
+ ) as span:
289
+ span.set_data("graphql.field_name", info.field_name)
290
+ span.set_data("graphql.parent_type", info.parent_type.name)
291
+ span.set_data("graphql.field_path", field_path)
292
+ span.set_data("graphql.path", ".".join(map(str, info.path.as_list())))
293
+
294
+ return _next(root, info, *args, **kwargs)
295
+
296
+
297
+ def _patch_views():
298
+ # type: () -> None
299
+ old_async_view_handle_errors = async_base_view.AsyncBaseHTTPView._handle_errors
300
+ old_sync_view_handle_errors = sync_base_view.SyncBaseHTTPView._handle_errors
301
+
302
+ def _sentry_patched_async_view_handle_errors(self, errors, response_data):
303
+ # type: (Any, List[GraphQLError], GraphQLHTTPResponse) -> None
304
+ old_async_view_handle_errors(self, errors, response_data)
305
+ _sentry_patched_handle_errors(self, errors, response_data)
306
+
307
+ def _sentry_patched_sync_view_handle_errors(self, errors, response_data):
308
+ # type: (Any, List[GraphQLError], GraphQLHTTPResponse) -> None
309
+ old_sync_view_handle_errors(self, errors, response_data)
310
+ _sentry_patched_handle_errors(self, errors, response_data)
311
+
312
+ @ensure_integration_enabled(StrawberryIntegration)
313
+ def _sentry_patched_handle_errors(self, errors, response_data):
314
+ # type: (Any, List[GraphQLError], GraphQLHTTPResponse) -> None
315
+ if not errors:
316
+ return
317
+
318
+ scope = sentry_sdk.get_isolation_scope()
319
+ event_processor = _make_response_event_processor(response_data)
320
+ scope.add_event_processor(event_processor)
321
+
322
+ with capture_internal_exceptions():
323
+ for error in errors:
324
+ event, hint = event_from_exception(
325
+ error,
326
+ client_options=sentry_sdk.get_client().options,
327
+ mechanism={
328
+ "type": StrawberryIntegration.identifier,
329
+ "handled": False,
330
+ },
331
+ )
332
+ sentry_sdk.capture_event(event, hint=hint)
333
+
334
+ async_base_view.AsyncBaseHTTPView._handle_errors = ( # type: ignore[method-assign]
335
+ _sentry_patched_async_view_handle_errors
336
+ )
337
+ sync_base_view.SyncBaseHTTPView._handle_errors = ( # type: ignore[method-assign]
338
+ _sentry_patched_sync_view_handle_errors
339
+ )
340
+
341
+
342
+ def _make_request_event_processor(execution_context):
343
+ # type: (ExecutionContext) -> EventProcessor
344
+
345
+ def inner(event, hint):
346
+ # type: (Event, dict[str, Any]) -> Event
347
+ with capture_internal_exceptions():
348
+ if should_send_default_pii():
349
+ request_data = event.setdefault("request", {})
350
+ request_data["api_target"] = "graphql"
351
+
352
+ if not request_data.get("data"):
353
+ data = {"query": execution_context.query} # type: dict[str, Any]
354
+ if execution_context.variables:
355
+ data["variables"] = execution_context.variables
356
+ if execution_context.operation_name:
357
+ data["operationName"] = execution_context.operation_name
358
+
359
+ request_data["data"] = data
360
+
361
+ else:
362
+ try:
363
+ del event["request"]["data"]
364
+ except (KeyError, TypeError):
365
+ pass
366
+
367
+ return event
368
+
369
+ return inner
370
+
371
+
372
+ def _make_response_event_processor(response_data):
373
+ # type: (GraphQLHTTPResponse) -> EventProcessor
374
+
375
+ def inner(event, hint):
376
+ # type: (Event, dict[str, Any]) -> Event
377
+ with capture_internal_exceptions():
378
+ if should_send_default_pii():
379
+ contexts = event.setdefault("contexts", {})
380
+ contexts["response"] = {"data": response_data}
381
+
382
+ return event
383
+
384
+ return inner
385
+
386
+
387
+ def _guess_if_using_async(extensions):
388
+ # type: (List[SchemaExtension]) -> Optional[bool]
389
+ if StrawberrySentryAsyncExtension in extensions:
390
+ return True
391
+ elif StrawberrySentrySyncExtension in extensions:
392
+ return False
393
+
394
+ return None
@@ -0,0 +1,70 @@
1
+ import functools
2
+ import sys
3
+
4
+ import sentry_sdk
5
+ from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
6
+ from sentry_sdk.integrations import Integration
7
+ from sentry_sdk._types import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+ from typing import NoReturn, Union
12
+
13
+
14
+ class SysExitIntegration(Integration):
15
+ """Captures sys.exit calls and sends them as events to Sentry.
16
+
17
+ By default, SystemExit exceptions are not captured by the SDK. Enabling this integration will capture SystemExit
18
+ exceptions generated by sys.exit calls and send them to Sentry.
19
+
20
+ This integration, in its default configuration, only captures the sys.exit call if the exit code is a non-zero and
21
+ non-None value (unsuccessful exits). Pass `capture_successful_exits=True` to capture successful exits as well.
22
+ Note that the integration does not capture SystemExit exceptions raised outside a call to sys.exit.
23
+ """
24
+
25
+ identifier = "sys_exit"
26
+
27
+ def __init__(self, *, capture_successful_exits=False):
28
+ # type: (bool) -> None
29
+ self._capture_successful_exits = capture_successful_exits
30
+
31
+ @staticmethod
32
+ def setup_once():
33
+ # type: () -> None
34
+ SysExitIntegration._patch_sys_exit()
35
+
36
+ @staticmethod
37
+ def _patch_sys_exit():
38
+ # type: () -> None
39
+ old_exit = sys.exit # type: Callable[[Union[str, int, None]], NoReturn]
40
+
41
+ @functools.wraps(old_exit)
42
+ def sentry_patched_exit(__status=0):
43
+ # type: (Union[str, int, None]) -> NoReturn
44
+ # @ensure_integration_enabled ensures that this is non-None
45
+ integration = sentry_sdk.get_client().get_integration(SysExitIntegration)
46
+ if integration is None:
47
+ old_exit(__status)
48
+
49
+ try:
50
+ old_exit(__status)
51
+ except SystemExit as e:
52
+ with capture_internal_exceptions():
53
+ if integration._capture_successful_exits or __status not in (
54
+ 0,
55
+ None,
56
+ ):
57
+ _capture_exception(e)
58
+ raise e
59
+
60
+ sys.exit = sentry_patched_exit
61
+
62
+
63
+ def _capture_exception(exc):
64
+ # type: (SystemExit) -> None
65
+ event, hint = event_from_exception(
66
+ exc,
67
+ client_options=sentry_sdk.get_client().options,
68
+ mechanism={"type": SysExitIntegration.identifier, "handled": False},
69
+ )
70
+ sentry_sdk.capture_event(event, hint=hint)
@@ -1,15 +1,22 @@
1
- from __future__ import absolute_import
2
-
3
1
  import sys
2
+ import warnings
3
+ from functools import wraps
4
4
  from threading import Thread, current_thread
5
+ from concurrent.futures import ThreadPoolExecutor, Future
5
6
 
6
- from sentry_sdk import Hub
7
- from sentry_sdk._compat import reraise
8
- from sentry_sdk._types import MYPY
7
+ import sentry_sdk
9
8
  from sentry_sdk.integrations import Integration
10
- from sentry_sdk.utils import event_from_exception, capture_internal_exceptions
9
+ from sentry_sdk.scope import use_isolation_scope, use_scope
10
+ from sentry_sdk.utils import (
11
+ event_from_exception,
12
+ capture_internal_exceptions,
13
+ logger,
14
+ reraise,
15
+ )
16
+
17
+ from typing import TYPE_CHECKING
11
18
 
12
- if MYPY:
19
+ if TYPE_CHECKING:
13
20
  from typing import Any
14
21
  from typing import TypeVar
15
22
  from typing import Callable
@@ -18,73 +25,170 @@ if MYPY:
18
25
  from sentry_sdk._types import ExcInfo
19
26
 
20
27
  F = TypeVar("F", bound=Callable[..., Any])
28
+ T = TypeVar("T", bound=Any)
21
29
 
22
30
 
23
31
  class ThreadingIntegration(Integration):
24
32
  identifier = "threading"
25
33
 
26
- def __init__(self, propagate_hub=False):
27
- # type: (bool) -> None
28
- self.propagate_hub = propagate_hub
34
+ def __init__(self, propagate_hub=None, propagate_scope=True):
35
+ # type: (Optional[bool], bool) -> None
36
+ if propagate_hub is not None:
37
+ logger.warning(
38
+ "Deprecated: propagate_hub is deprecated. This will be removed in the future."
39
+ )
40
+
41
+ # Note: propagate_hub did not have any effect on propagation of scope data
42
+ # scope data was always propagated no matter what the value of propagate_hub was
43
+ # This is why the default for propagate_scope is True
44
+
45
+ self.propagate_scope = propagate_scope
46
+
47
+ if propagate_hub is not None:
48
+ self.propagate_scope = propagate_hub
29
49
 
30
50
  @staticmethod
31
51
  def setup_once():
32
52
  # type: () -> None
33
53
  old_start = Thread.start
34
54
 
55
+ try:
56
+ from django import VERSION as django_version # noqa: N811
57
+ import channels # type: ignore[import-untyped]
58
+
59
+ channels_version = channels.__version__
60
+ except ImportError:
61
+ django_version = None
62
+ channels_version = None
63
+
64
+ is_async_emulated_with_threads = (
65
+ sys.version_info < (3, 9)
66
+ and channels_version is not None
67
+ and channels_version < "4.0.0"
68
+ and django_version is not None
69
+ and django_version >= (3, 0)
70
+ and django_version < (4, 0)
71
+ )
72
+
73
+ @wraps(old_start)
35
74
  def sentry_start(self, *a, **kw):
36
75
  # type: (Thread, *Any, **Any) -> Any
37
- hub = Hub.current
38
- integration = hub.get_integration(ThreadingIntegration)
39
- if integration is not None:
40
- if not integration.propagate_hub:
41
- hub_ = None
76
+ integration = sentry_sdk.get_client().get_integration(ThreadingIntegration)
77
+ if integration is None:
78
+ return old_start(self, *a, **kw)
79
+
80
+ if integration.propagate_scope:
81
+ if is_async_emulated_with_threads:
82
+ warnings.warn(
83
+ "There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. "
84
+ "(Async support is emulated using threads and some Sentry data may be leaked between those threads.) "
85
+ "Please either upgrade to Django channels 4.0+, use Django's async features "
86
+ "available in Django 3.1+ instead of Django channels, or upgrade to Python 3.9+.",
87
+ stacklevel=2,
88
+ )
89
+ isolation_scope = sentry_sdk.get_isolation_scope()
90
+ current_scope = sentry_sdk.get_current_scope()
91
+
42
92
  else:
43
- hub_ = Hub(hub)
44
- # Patching instance methods in `start()` creates a reference cycle if
45
- # done in a naive way. See
46
- # https://github.com/getsentry/sentry-python/pull/434
47
- #
48
- # In threading module, using current_thread API will access current thread instance
49
- # without holding it to avoid a reference cycle in an easier way.
50
- with capture_internal_exceptions():
51
- new_run = _wrap_run(hub_, getattr(self.run, "__func__", self.run))
52
- self.run = new_run # type: ignore
53
-
54
- return old_start(self, *a, **kw) # type: ignore
93
+ isolation_scope = sentry_sdk.get_isolation_scope().fork()
94
+ current_scope = sentry_sdk.get_current_scope().fork()
95
+ else:
96
+ isolation_scope = None
97
+ current_scope = None
98
+
99
+ # Patching instance methods in `start()` creates a reference cycle if
100
+ # done in a naive way. See
101
+ # https://github.com/getsentry/sentry-python/pull/434
102
+ #
103
+ # In threading module, using current_thread API will access current thread instance
104
+ # without holding it to avoid a reference cycle in an easier way.
105
+ with capture_internal_exceptions():
106
+ new_run = _wrap_run(
107
+ isolation_scope,
108
+ current_scope,
109
+ getattr(self.run, "__func__", self.run),
110
+ )
111
+ self.run = new_run # type: ignore
112
+
113
+ return old_start(self, *a, **kw)
55
114
 
56
115
  Thread.start = sentry_start # type: ignore
116
+ ThreadPoolExecutor.submit = _wrap_threadpool_executor_submit( # type: ignore
117
+ ThreadPoolExecutor.submit, is_async_emulated_with_threads
118
+ )
57
119
 
58
120
 
59
- def _wrap_run(parent_hub, old_run_func):
60
- # type: (Optional[Hub], F) -> F
121
+ def _wrap_run(isolation_scope_to_use, current_scope_to_use, old_run_func):
122
+ # type: (Optional[sentry_sdk.Scope], Optional[sentry_sdk.Scope], F) -> F
123
+ @wraps(old_run_func)
61
124
  def run(*a, **kw):
62
125
  # type: (*Any, **Any) -> Any
63
- hub = parent_hub or Hub.current
64
- with hub:
126
+ def _run_old_run_func():
127
+ # type: () -> Any
65
128
  try:
66
129
  self = current_thread()
67
- return old_run_func(self, *a, **kw)
130
+ return old_run_func(self, *a[1:], **kw)
68
131
  except Exception:
69
132
  reraise(*_capture_exception())
70
133
 
134
+ if isolation_scope_to_use is not None and current_scope_to_use is not None:
135
+ with use_isolation_scope(isolation_scope_to_use):
136
+ with use_scope(current_scope_to_use):
137
+ return _run_old_run_func()
138
+ else:
139
+ return _run_old_run_func()
140
+
71
141
  return run # type: ignore
72
142
 
73
143
 
144
+ def _wrap_threadpool_executor_submit(func, is_async_emulated_with_threads):
145
+ # type: (Callable[..., Future[T]], bool) -> Callable[..., Future[T]]
146
+ """
147
+ Wrap submit call to propagate scopes on task submission.
148
+ """
149
+
150
+ @wraps(func)
151
+ def sentry_submit(self, fn, *args, **kwargs):
152
+ # type: (ThreadPoolExecutor, Callable[..., T], *Any, **Any) -> Future[T]
153
+ integration = sentry_sdk.get_client().get_integration(ThreadingIntegration)
154
+ if integration is None:
155
+ return func(self, fn, *args, **kwargs)
156
+
157
+ if integration.propagate_scope and is_async_emulated_with_threads:
158
+ isolation_scope = sentry_sdk.get_isolation_scope()
159
+ current_scope = sentry_sdk.get_current_scope()
160
+ elif integration.propagate_scope:
161
+ isolation_scope = sentry_sdk.get_isolation_scope().fork()
162
+ current_scope = sentry_sdk.get_current_scope().fork()
163
+ else:
164
+ isolation_scope = None
165
+ current_scope = None
166
+
167
+ def wrapped_fn(*args, **kwargs):
168
+ # type: (*Any, **Any) -> Any
169
+ if isolation_scope is not None and current_scope is not None:
170
+ with use_isolation_scope(isolation_scope):
171
+ with use_scope(current_scope):
172
+ return fn(*args, **kwargs)
173
+
174
+ return fn(*args, **kwargs)
175
+
176
+ return func(self, wrapped_fn, *args, **kwargs)
177
+
178
+ return sentry_submit
179
+
180
+
74
181
  def _capture_exception():
75
182
  # type: () -> ExcInfo
76
- hub = Hub.current
77
183
  exc_info = sys.exc_info()
78
184
 
79
- if hub.get_integration(ThreadingIntegration) is not None:
80
- # If an integration is there, a client has to be there.
81
- client = hub.client # type: Any
82
-
185
+ client = sentry_sdk.get_client()
186
+ if client.get_integration(ThreadingIntegration) is not None:
83
187
  event, hint = event_from_exception(
84
188
  exc_info,
85
189
  client_options=client.options,
86
190
  mechanism={"type": "threading", "handled": False},
87
191
  )
88
- hub.capture_event(event, hint=hint)
192
+ sentry_sdk.capture_event(event, hint=hint)
89
193
 
90
194
  return exc_info