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,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,64 +1,194 @@
1
- from __future__ import absolute_import
2
-
3
1
  import sys
2
+ import warnings
3
+ from functools import wraps
4
+ from threading import Thread, current_thread
5
+ from concurrent.futures import ThreadPoolExecutor, Future
4
6
 
5
- from threading import Thread
6
-
7
- from sentry_sdk import Hub
8
- from sentry_sdk._compat import reraise
9
- from sentry_sdk.utils import event_from_exception
7
+ import sentry_sdk
10
8
  from sentry_sdk.integrations import Integration
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
18
+
19
+ if TYPE_CHECKING:
20
+ from typing import Any
21
+ from typing import TypeVar
22
+ from typing import Callable
23
+ from typing import Optional
24
+
25
+ from sentry_sdk._types import ExcInfo
26
+
27
+ F = TypeVar("F", bound=Callable[..., Any])
28
+ T = TypeVar("T", bound=Any)
11
29
 
12
30
 
13
31
  class ThreadingIntegration(Integration):
14
32
  identifier = "threading"
15
33
 
16
- def __init__(self, propagate_hub=False):
17
- 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
18
49
 
19
50
  @staticmethod
20
51
  def setup_once():
52
+ # type: () -> None
21
53
  old_start = Thread.start
22
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)
23
74
  def sentry_start(self, *a, **kw):
24
- hub = Hub.current
25
- integration = hub.get_integration(ThreadingIntegration)
26
- if integration is not None:
27
- if integration.propagate_hub:
28
- hub = Hub(hub)
29
- else:
30
- hub = None
75
+ # type: (Thread, *Any, **Any) -> Any
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()
31
91
 
32
- self.run = _wrap_run(hub, self.run)
92
+ else:
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
33
112
 
34
113
  return old_start(self, *a, **kw)
35
114
 
36
- Thread.start = sentry_start
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
+ )
37
119
 
38
120
 
39
- def _wrap_run(parent_hub, old_run):
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)
40
124
  def run(*a, **kw):
41
- hub = parent_hub or Hub.current
42
-
43
- with hub:
125
+ # type: (*Any, **Any) -> Any
126
+ def _run_old_run_func():
127
+ # type: () -> Any
44
128
  try:
45
- return old_run(*a, **kw)
129
+ self = current_thread()
130
+ return old_run_func(self, *a[1:], **kw)
46
131
  except Exception:
47
132
  reraise(*_capture_exception())
48
133
 
49
- return run
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
+
141
+ return run # type: ignore
142
+
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
50
179
 
51
180
 
52
181
  def _capture_exception():
53
- hub = Hub.current
182
+ # type: () -> ExcInfo
54
183
  exc_info = sys.exc_info()
55
184
 
56
- if hub.get_integration(ThreadingIntegration) is not None:
185
+ client = sentry_sdk.get_client()
186
+ if client.get_integration(ThreadingIntegration) is not None:
57
187
  event, hint = event_from_exception(
58
188
  exc_info,
59
- client_options=hub.client.options,
189
+ client_options=client.options,
60
190
  mechanism={"type": "threading", "handled": False},
61
191
  )
62
- hub.capture_event(event, hint=hint)
192
+ sentry_sdk.capture_event(event, hint=hint)
63
193
 
64
194
  return exc_info