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