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,214 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import sentry_sdk
|
|
5
|
+
from sentry_sdk.consts import SPANSTATUS, SPANDATA, OP
|
|
6
|
+
from sentry_sdk.integrations import DidNotEnable, Integration
|
|
7
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
8
|
+
from sentry_sdk.tracing import Span
|
|
9
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from pymongo import monitoring
|
|
13
|
+
except ImportError:
|
|
14
|
+
raise DidNotEnable("Pymongo not installed")
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from typing import Any, Dict, Union
|
|
20
|
+
|
|
21
|
+
from pymongo.monitoring import (
|
|
22
|
+
CommandFailedEvent,
|
|
23
|
+
CommandStartedEvent,
|
|
24
|
+
CommandSucceededEvent,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
SAFE_COMMAND_ATTRIBUTES = [
|
|
29
|
+
"insert",
|
|
30
|
+
"ordered",
|
|
31
|
+
"find",
|
|
32
|
+
"limit",
|
|
33
|
+
"singleBatch",
|
|
34
|
+
"aggregate",
|
|
35
|
+
"createIndexes",
|
|
36
|
+
"indexes",
|
|
37
|
+
"delete",
|
|
38
|
+
"findAndModify",
|
|
39
|
+
"renameCollection",
|
|
40
|
+
"to",
|
|
41
|
+
"drop",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _strip_pii(command):
|
|
46
|
+
# type: (Dict[str, Any]) -> Dict[str, Any]
|
|
47
|
+
for key in command:
|
|
48
|
+
is_safe_field = key in SAFE_COMMAND_ATTRIBUTES
|
|
49
|
+
if is_safe_field:
|
|
50
|
+
# Skip if safe key
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
update_db_command = key == "update" and "findAndModify" not in command
|
|
54
|
+
if update_db_command:
|
|
55
|
+
# Also skip "update" db command because it is save.
|
|
56
|
+
# There is also an "update" key in the "findAndModify" command, which is NOT safe!
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# Special stripping for documents
|
|
60
|
+
is_document = key == "documents"
|
|
61
|
+
if is_document:
|
|
62
|
+
for doc in command[key]:
|
|
63
|
+
for doc_key in doc:
|
|
64
|
+
doc[doc_key] = "%s"
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# Special stripping for dict style fields
|
|
68
|
+
is_dict_field = key in ["filter", "query", "update"]
|
|
69
|
+
if is_dict_field:
|
|
70
|
+
for item_key in command[key]:
|
|
71
|
+
command[key][item_key] = "%s"
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# For pipeline fields strip the `$match` dict
|
|
75
|
+
is_pipeline_field = key == "pipeline"
|
|
76
|
+
if is_pipeline_field:
|
|
77
|
+
for pipeline in command[key]:
|
|
78
|
+
for match_key in pipeline["$match"] if "$match" in pipeline else []:
|
|
79
|
+
pipeline["$match"][match_key] = "%s"
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Default stripping
|
|
83
|
+
command[key] = "%s"
|
|
84
|
+
|
|
85
|
+
return command
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_db_data(event):
|
|
89
|
+
# type: (Any) -> Dict[str, Any]
|
|
90
|
+
data = {}
|
|
91
|
+
|
|
92
|
+
data[SPANDATA.DB_SYSTEM] = "mongodb"
|
|
93
|
+
|
|
94
|
+
db_name = event.database_name
|
|
95
|
+
if db_name is not None:
|
|
96
|
+
data[SPANDATA.DB_NAME] = db_name
|
|
97
|
+
|
|
98
|
+
server_address = event.connection_id[0]
|
|
99
|
+
if server_address is not None:
|
|
100
|
+
data[SPANDATA.SERVER_ADDRESS] = server_address
|
|
101
|
+
|
|
102
|
+
server_port = event.connection_id[1]
|
|
103
|
+
if server_port is not None:
|
|
104
|
+
data[SPANDATA.SERVER_PORT] = server_port
|
|
105
|
+
|
|
106
|
+
return data
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class CommandTracer(monitoring.CommandListener):
|
|
110
|
+
def __init__(self):
|
|
111
|
+
# type: () -> None
|
|
112
|
+
self._ongoing_operations = {} # type: Dict[int, Span]
|
|
113
|
+
|
|
114
|
+
def _operation_key(self, event):
|
|
115
|
+
# type: (Union[CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent]) -> int
|
|
116
|
+
return event.request_id
|
|
117
|
+
|
|
118
|
+
def started(self, event):
|
|
119
|
+
# type: (CommandStartedEvent) -> None
|
|
120
|
+
if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
with capture_internal_exceptions():
|
|
124
|
+
command = dict(copy.deepcopy(event.command))
|
|
125
|
+
|
|
126
|
+
command.pop("$db", None)
|
|
127
|
+
command.pop("$clusterTime", None)
|
|
128
|
+
command.pop("$signature", None)
|
|
129
|
+
|
|
130
|
+
tags = {
|
|
131
|
+
"db.name": event.database_name,
|
|
132
|
+
SPANDATA.DB_SYSTEM: "mongodb",
|
|
133
|
+
SPANDATA.DB_OPERATION: event.command_name,
|
|
134
|
+
SPANDATA.DB_MONGODB_COLLECTION: command.get(event.command_name),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
tags["net.peer.name"] = event.connection_id[0]
|
|
139
|
+
tags["net.peer.port"] = str(event.connection_id[1])
|
|
140
|
+
except TypeError:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
data = {"operation_ids": {}} # type: Dict[str, Any]
|
|
144
|
+
data["operation_ids"]["operation"] = event.operation_id
|
|
145
|
+
data["operation_ids"]["request"] = event.request_id
|
|
146
|
+
|
|
147
|
+
data.update(_get_db_data(event))
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
lsid = command.pop("lsid")["id"]
|
|
151
|
+
data["operation_ids"]["session"] = str(lsid)
|
|
152
|
+
except KeyError:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
if not should_send_default_pii():
|
|
156
|
+
command = _strip_pii(command)
|
|
157
|
+
|
|
158
|
+
query = json.dumps(command, default=str)
|
|
159
|
+
span = sentry_sdk.start_span(
|
|
160
|
+
op=OP.DB,
|
|
161
|
+
name=query,
|
|
162
|
+
origin=PyMongoIntegration.origin,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
for tag, value in tags.items():
|
|
166
|
+
# set the tag for backwards-compatibility.
|
|
167
|
+
# TODO: remove the set_tag call in the next major release!
|
|
168
|
+
span.set_tag(tag, value)
|
|
169
|
+
|
|
170
|
+
span.set_data(tag, value)
|
|
171
|
+
|
|
172
|
+
for key, value in data.items():
|
|
173
|
+
span.set_data(key, value)
|
|
174
|
+
|
|
175
|
+
with capture_internal_exceptions():
|
|
176
|
+
sentry_sdk.add_breadcrumb(
|
|
177
|
+
message=query, category="query", type=OP.DB, data=tags
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self._ongoing_operations[self._operation_key(event)] = span.__enter__()
|
|
181
|
+
|
|
182
|
+
def failed(self, event):
|
|
183
|
+
# type: (CommandFailedEvent) -> None
|
|
184
|
+
if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None:
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
span = self._ongoing_operations.pop(self._operation_key(event))
|
|
189
|
+
span.set_status(SPANSTATUS.INTERNAL_ERROR)
|
|
190
|
+
span.__exit__(None, None, None)
|
|
191
|
+
except KeyError:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
def succeeded(self, event):
|
|
195
|
+
# type: (CommandSucceededEvent) -> None
|
|
196
|
+
if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
span = self._ongoing_operations.pop(self._operation_key(event))
|
|
201
|
+
span.set_status(SPANSTATUS.OK)
|
|
202
|
+
span.__exit__(None, None, None)
|
|
203
|
+
except KeyError:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class PyMongoIntegration(Integration):
|
|
208
|
+
identifier = "pymongo"
|
|
209
|
+
origin = f"auto.db.{identifier}"
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def setup_once():
|
|
213
|
+
# type: () -> None
|
|
214
|
+
monitoring.register(CommandTracer())
|
|
@@ -1,34 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import functools
|
|
3
2
|
import os
|
|
4
3
|
import sys
|
|
5
4
|
import weakref
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
from sentry_sdk.hub import Hub, _should_send_default_pii
|
|
11
|
-
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
|
|
12
|
-
from sentry_sdk._compat import reraise, iteritems
|
|
13
|
-
|
|
14
|
-
from sentry_sdk.integrations import Integration
|
|
6
|
+
import sentry_sdk
|
|
7
|
+
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
15
8
|
from sentry_sdk.integrations._wsgi_common import RequestExtractor
|
|
16
9
|
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
|
17
|
-
|
|
18
|
-
from sentry_sdk.
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
11
|
+
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
|
12
|
+
from sentry_sdk.utils import (
|
|
13
|
+
capture_internal_exceptions,
|
|
14
|
+
ensure_integration_enabled,
|
|
15
|
+
event_from_exception,
|
|
16
|
+
reraise,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from pyramid.httpexceptions import HTTPException
|
|
21
|
+
from pyramid.request import Request
|
|
22
|
+
except ImportError:
|
|
23
|
+
raise DidNotEnable("Pyramid not installed")
|
|
24
|
+
|
|
25
|
+
from typing import TYPE_CHECKING
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
21
28
|
from pyramid.response import Response
|
|
22
29
|
from typing import Any
|
|
23
30
|
from sentry_sdk.integrations.wsgi import _ScopedResponse
|
|
24
31
|
from typing import Callable
|
|
25
32
|
from typing import Dict
|
|
26
33
|
from typing import Optional
|
|
27
|
-
from webob.cookies import RequestCookies
|
|
28
|
-
from webob.
|
|
34
|
+
from webob.cookies import RequestCookies
|
|
35
|
+
from webob.request import _FieldStorageWithFile
|
|
29
36
|
|
|
30
37
|
from sentry_sdk.utils import ExcInfo
|
|
31
|
-
from sentry_sdk._types import EventProcessor
|
|
38
|
+
from sentry_sdk._types import Event, EventProcessor
|
|
32
39
|
|
|
33
40
|
|
|
34
41
|
if getattr(Request, "authenticated_userid", None):
|
|
@@ -37,7 +44,6 @@ if getattr(Request, "authenticated_userid", None):
|
|
|
37
44
|
# type: (Request) -> Optional[Any]
|
|
38
45
|
return request.authenticated_userid
|
|
39
46
|
|
|
40
|
-
|
|
41
47
|
else:
|
|
42
48
|
# bw-compat for pyramid < 1.5
|
|
43
49
|
from pyramid.security import authenticated_userid # type: ignore
|
|
@@ -48,8 +54,9 @@ TRANSACTION_STYLE_VALUES = ("route_name", "route_pattern")
|
|
|
48
54
|
|
|
49
55
|
class PyramidIntegration(Integration):
|
|
50
56
|
identifier = "pyramid"
|
|
57
|
+
origin = f"auto.http.{identifier}"
|
|
51
58
|
|
|
52
|
-
transaction_style =
|
|
59
|
+
transaction_style = ""
|
|
53
60
|
|
|
54
61
|
def __init__(self, transaction_style="route_name"):
|
|
55
62
|
# type: (str) -> None
|
|
@@ -64,28 +71,23 @@ class PyramidIntegration(Integration):
|
|
|
64
71
|
def setup_once():
|
|
65
72
|
# type: () -> None
|
|
66
73
|
from pyramid import router
|
|
67
|
-
from pyramid.request import Request
|
|
68
74
|
|
|
69
75
|
old_call_view = router._call_view
|
|
70
76
|
|
|
77
|
+
@functools.wraps(old_call_view)
|
|
71
78
|
def sentry_patched_call_view(registry, request, *args, **kwargs):
|
|
72
79
|
# type: (Any, Request, *Any, **Any) -> Response
|
|
73
|
-
|
|
74
|
-
integration
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
pass
|
|
85
|
-
|
|
86
|
-
scope.add_event_processor(
|
|
87
|
-
_make_event_processor(weakref.ref(request), integration)
|
|
88
|
-
)
|
|
80
|
+
integration = sentry_sdk.get_client().get_integration(PyramidIntegration)
|
|
81
|
+
if integration is None:
|
|
82
|
+
return old_call_view(registry, request, *args, **kwargs)
|
|
83
|
+
|
|
84
|
+
_set_transaction_name_and_source(
|
|
85
|
+
sentry_sdk.get_current_scope(), integration.transaction_style, request
|
|
86
|
+
)
|
|
87
|
+
scope = sentry_sdk.get_isolation_scope()
|
|
88
|
+
scope.add_event_processor(
|
|
89
|
+
_make_event_processor(weakref.ref(request), integration)
|
|
90
|
+
)
|
|
89
91
|
|
|
90
92
|
return old_call_view(registry, request, *args, **kwargs)
|
|
91
93
|
|
|
@@ -102,7 +104,8 @@ class PyramidIntegration(Integration):
|
|
|
102
104
|
self.exc_info
|
|
103
105
|
and all(self.exc_info)
|
|
104
106
|
and rv.status_int == 500
|
|
105
|
-
and
|
|
107
|
+
and sentry_sdk.get_client().get_integration(PyramidIntegration)
|
|
108
|
+
is not None
|
|
106
109
|
):
|
|
107
110
|
_capture_exception(self.exc_info)
|
|
108
111
|
|
|
@@ -112,13 +115,9 @@ class PyramidIntegration(Integration):
|
|
|
112
115
|
|
|
113
116
|
old_wsgi_call = router.Router.__call__
|
|
114
117
|
|
|
118
|
+
@ensure_integration_enabled(PyramidIntegration, old_wsgi_call)
|
|
115
119
|
def sentry_patched_wsgi_call(self, environ, start_response):
|
|
116
120
|
# type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
|
|
117
|
-
hub = Hub.current
|
|
118
|
-
integration = hub.get_integration(PyramidIntegration)
|
|
119
|
-
if integration is None:
|
|
120
|
-
return old_wsgi_call(self, environ, start_response)
|
|
121
|
-
|
|
122
121
|
def sentry_patched_inner_wsgi_call(environ, start_response):
|
|
123
122
|
# type: (Dict[str, Any], Callable[..., Any]) -> Any
|
|
124
123
|
try:
|
|
@@ -128,31 +127,43 @@ class PyramidIntegration(Integration):
|
|
|
128
127
|
_capture_exception(einfo)
|
|
129
128
|
reraise(*einfo)
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
middleware = SentryWsgiMiddleware(
|
|
131
|
+
sentry_patched_inner_wsgi_call,
|
|
132
|
+
span_origin=PyramidIntegration.origin,
|
|
133
133
|
)
|
|
134
|
+
return middleware(environ, start_response)
|
|
134
135
|
|
|
135
136
|
router.Router.__call__ = sentry_patched_wsgi_call
|
|
136
137
|
|
|
137
138
|
|
|
139
|
+
@ensure_integration_enabled(PyramidIntegration)
|
|
138
140
|
def _capture_exception(exc_info):
|
|
139
141
|
# type: (ExcInfo) -> None
|
|
140
142
|
if exc_info[0] is None or issubclass(exc_info[0], HTTPException):
|
|
141
143
|
return
|
|
142
|
-
hub = Hub.current
|
|
143
|
-
if hub.get_integration(PyramidIntegration) is None:
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
# If an integration is there, a client has to be there.
|
|
147
|
-
client = hub.client # type: Any
|
|
148
144
|
|
|
149
145
|
event, hint = event_from_exception(
|
|
150
146
|
exc_info,
|
|
151
|
-
client_options=
|
|
147
|
+
client_options=sentry_sdk.get_client().options,
|
|
152
148
|
mechanism={"type": "pyramid", "handled": False},
|
|
153
149
|
)
|
|
154
150
|
|
|
155
|
-
|
|
151
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _set_transaction_name_and_source(scope, transaction_style, request):
|
|
155
|
+
# type: (sentry_sdk.Scope, str, Request) -> None
|
|
156
|
+
try:
|
|
157
|
+
name_for_style = {
|
|
158
|
+
"route_name": request.matched_route.name,
|
|
159
|
+
"route_pattern": request.matched_route.pattern,
|
|
160
|
+
}
|
|
161
|
+
scope.set_transaction_name(
|
|
162
|
+
name_for_style[transaction_style],
|
|
163
|
+
source=SOURCE_FOR_STYLE[transaction_style],
|
|
164
|
+
)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
156
167
|
|
|
157
168
|
|
|
158
169
|
class PyramidRequestExtractor(RequestExtractor):
|
|
@@ -176,20 +187,20 @@ class PyramidRequestExtractor(RequestExtractor):
|
|
|
176
187
|
# type: () -> Dict[str, str]
|
|
177
188
|
return {
|
|
178
189
|
key: value
|
|
179
|
-
for key, value in
|
|
190
|
+
for key, value in self.request.POST.items()
|
|
180
191
|
if not getattr(value, "filename", None)
|
|
181
192
|
}
|
|
182
193
|
|
|
183
194
|
def files(self):
|
|
184
|
-
# type: () -> Dict[str,
|
|
195
|
+
# type: () -> Dict[str, _FieldStorageWithFile]
|
|
185
196
|
return {
|
|
186
197
|
key: value
|
|
187
|
-
for key, value in
|
|
198
|
+
for key, value in self.request.POST.items()
|
|
188
199
|
if getattr(value, "filename", None)
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
def size_of_file(self, postdata):
|
|
192
|
-
# type: (
|
|
203
|
+
# type: (_FieldStorageWithFile) -> int
|
|
193
204
|
file = postdata.file
|
|
194
205
|
try:
|
|
195
206
|
return os.fstat(file.fileno()).st_size
|
|
@@ -199,8 +210,8 @@ class PyramidRequestExtractor(RequestExtractor):
|
|
|
199
210
|
|
|
200
211
|
def _make_event_processor(weak_request, integration):
|
|
201
212
|
# type: (Callable[[], Request], PyramidIntegration) -> EventProcessor
|
|
202
|
-
def
|
|
203
|
-
# type: (
|
|
213
|
+
def pyramid_event_processor(event, hint):
|
|
214
|
+
# type: (Event, Dict[str, Any]) -> Event
|
|
204
215
|
request = weak_request()
|
|
205
216
|
if request is None:
|
|
206
217
|
return event
|
|
@@ -208,11 +219,11 @@ def _make_event_processor(weak_request, integration):
|
|
|
208
219
|
with capture_internal_exceptions():
|
|
209
220
|
PyramidRequestExtractor(request).extract_into_event(event)
|
|
210
221
|
|
|
211
|
-
if
|
|
222
|
+
if should_send_default_pii():
|
|
212
223
|
with capture_internal_exceptions():
|
|
213
224
|
user_info = event.setdefault("user", {})
|
|
214
225
|
user_info.setdefault("id", authenticated_userid(request))
|
|
215
226
|
|
|
216
227
|
return event
|
|
217
228
|
|
|
218
|
-
return
|
|
229
|
+
return pyramid_event_processor
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
import sentry_sdk
|
|
6
|
+
from sentry_sdk.integrations import DidNotEnable, Integration
|
|
7
|
+
from sentry_sdk.integrations._wsgi_common import _filter_headers
|
|
8
|
+
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
|
|
9
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
10
|
+
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
|
11
|
+
from sentry_sdk.utils import (
|
|
12
|
+
capture_internal_exceptions,
|
|
13
|
+
ensure_integration_enabled,
|
|
14
|
+
event_from_exception,
|
|
15
|
+
)
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from typing import Any
|
|
20
|
+
from typing import Union
|
|
21
|
+
|
|
22
|
+
from sentry_sdk._types import Event, EventProcessor
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import quart_auth # type: ignore
|
|
26
|
+
except ImportError:
|
|
27
|
+
quart_auth = None
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from quart import ( # type: ignore
|
|
31
|
+
has_request_context,
|
|
32
|
+
has_websocket_context,
|
|
33
|
+
Request,
|
|
34
|
+
Quart,
|
|
35
|
+
request,
|
|
36
|
+
websocket,
|
|
37
|
+
)
|
|
38
|
+
from quart.signals import ( # type: ignore
|
|
39
|
+
got_background_exception,
|
|
40
|
+
got_request_exception,
|
|
41
|
+
got_websocket_exception,
|
|
42
|
+
request_started,
|
|
43
|
+
websocket_started,
|
|
44
|
+
)
|
|
45
|
+
except ImportError:
|
|
46
|
+
raise DidNotEnable("Quart is not installed")
|
|
47
|
+
else:
|
|
48
|
+
# Quart 0.19 is based on Flask and hence no longer has a Scaffold
|
|
49
|
+
try:
|
|
50
|
+
from quart.scaffold import Scaffold # type: ignore
|
|
51
|
+
except ImportError:
|
|
52
|
+
from flask.sansio.scaffold import Scaffold # type: ignore
|
|
53
|
+
|
|
54
|
+
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class QuartIntegration(Integration):
|
|
58
|
+
identifier = "quart"
|
|
59
|
+
origin = f"auto.http.{identifier}"
|
|
60
|
+
|
|
61
|
+
transaction_style = ""
|
|
62
|
+
|
|
63
|
+
def __init__(self, transaction_style="endpoint"):
|
|
64
|
+
# type: (str) -> None
|
|
65
|
+
if transaction_style not in TRANSACTION_STYLE_VALUES:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"Invalid value for transaction_style: %s (must be in %s)"
|
|
68
|
+
% (transaction_style, TRANSACTION_STYLE_VALUES)
|
|
69
|
+
)
|
|
70
|
+
self.transaction_style = transaction_style
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def setup_once():
|
|
74
|
+
# type: () -> None
|
|
75
|
+
|
|
76
|
+
request_started.connect(_request_websocket_started)
|
|
77
|
+
websocket_started.connect(_request_websocket_started)
|
|
78
|
+
got_background_exception.connect(_capture_exception)
|
|
79
|
+
got_request_exception.connect(_capture_exception)
|
|
80
|
+
got_websocket_exception.connect(_capture_exception)
|
|
81
|
+
|
|
82
|
+
patch_asgi_app()
|
|
83
|
+
patch_scaffold_route()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def patch_asgi_app():
|
|
87
|
+
# type: () -> None
|
|
88
|
+
old_app = Quart.__call__
|
|
89
|
+
|
|
90
|
+
async def sentry_patched_asgi_app(self, scope, receive, send):
|
|
91
|
+
# type: (Any, Any, Any, Any) -> Any
|
|
92
|
+
if sentry_sdk.get_client().get_integration(QuartIntegration) is None:
|
|
93
|
+
return await old_app(self, scope, receive, send)
|
|
94
|
+
|
|
95
|
+
middleware = SentryAsgiMiddleware(
|
|
96
|
+
lambda *a, **kw: old_app(self, *a, **kw),
|
|
97
|
+
span_origin=QuartIntegration.origin,
|
|
98
|
+
asgi_version=3,
|
|
99
|
+
)
|
|
100
|
+
return await middleware(scope, receive, send)
|
|
101
|
+
|
|
102
|
+
Quart.__call__ = sentry_patched_asgi_app
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def patch_scaffold_route():
|
|
106
|
+
# type: () -> None
|
|
107
|
+
old_route = Scaffold.route
|
|
108
|
+
|
|
109
|
+
def _sentry_route(*args, **kwargs):
|
|
110
|
+
# type: (*Any, **Any) -> Any
|
|
111
|
+
old_decorator = old_route(*args, **kwargs)
|
|
112
|
+
|
|
113
|
+
def decorator(old_func):
|
|
114
|
+
# type: (Any) -> Any
|
|
115
|
+
|
|
116
|
+
if inspect.isfunction(old_func) and not asyncio.iscoroutinefunction(
|
|
117
|
+
old_func
|
|
118
|
+
):
|
|
119
|
+
|
|
120
|
+
@wraps(old_func)
|
|
121
|
+
@ensure_integration_enabled(QuartIntegration, old_func)
|
|
122
|
+
def _sentry_func(*args, **kwargs):
|
|
123
|
+
# type: (*Any, **Any) -> Any
|
|
124
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
125
|
+
if current_scope.transaction is not None:
|
|
126
|
+
current_scope.transaction.update_active_thread()
|
|
127
|
+
|
|
128
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
129
|
+
if sentry_scope.profile is not None:
|
|
130
|
+
sentry_scope.profile.update_active_thread_id()
|
|
131
|
+
|
|
132
|
+
return old_func(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
return old_decorator(_sentry_func)
|
|
135
|
+
|
|
136
|
+
return old_decorator(old_func)
|
|
137
|
+
|
|
138
|
+
return decorator
|
|
139
|
+
|
|
140
|
+
Scaffold.route = _sentry_route
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _set_transaction_name_and_source(scope, transaction_style, request):
|
|
144
|
+
# type: (sentry_sdk.Scope, str, Request) -> None
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
name_for_style = {
|
|
148
|
+
"url": request.url_rule.rule,
|
|
149
|
+
"endpoint": request.url_rule.endpoint,
|
|
150
|
+
}
|
|
151
|
+
scope.set_transaction_name(
|
|
152
|
+
name_for_style[transaction_style],
|
|
153
|
+
source=SOURCE_FOR_STYLE[transaction_style],
|
|
154
|
+
)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def _request_websocket_started(app, **kwargs):
|
|
160
|
+
# type: (Quart, **Any) -> None
|
|
161
|
+
integration = sentry_sdk.get_client().get_integration(QuartIntegration)
|
|
162
|
+
if integration is None:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
if has_request_context():
|
|
166
|
+
request_websocket = request._get_current_object()
|
|
167
|
+
if has_websocket_context():
|
|
168
|
+
request_websocket = websocket._get_current_object()
|
|
169
|
+
|
|
170
|
+
# Set the transaction name here, but rely on ASGI middleware
|
|
171
|
+
# to actually start the transaction
|
|
172
|
+
_set_transaction_name_and_source(
|
|
173
|
+
sentry_sdk.get_current_scope(), integration.transaction_style, request_websocket
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
scope = sentry_sdk.get_isolation_scope()
|
|
177
|
+
evt_processor = _make_request_event_processor(app, request_websocket, integration)
|
|
178
|
+
scope.add_event_processor(evt_processor)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _make_request_event_processor(app, request, integration):
|
|
182
|
+
# type: (Quart, Request, QuartIntegration) -> EventProcessor
|
|
183
|
+
def inner(event, hint):
|
|
184
|
+
# type: (Event, dict[str, Any]) -> Event
|
|
185
|
+
# if the request is gone we are fine not logging the data from
|
|
186
|
+
# it. This might happen if the processor is pushed away to
|
|
187
|
+
# another thread.
|
|
188
|
+
if request is None:
|
|
189
|
+
return event
|
|
190
|
+
|
|
191
|
+
with capture_internal_exceptions():
|
|
192
|
+
# TODO: Figure out what to do with request body. Methods on request
|
|
193
|
+
# are async, but event processors are not.
|
|
194
|
+
|
|
195
|
+
request_info = event.setdefault("request", {})
|
|
196
|
+
request_info["url"] = request.url
|
|
197
|
+
request_info["query_string"] = request.query_string
|
|
198
|
+
request_info["method"] = request.method
|
|
199
|
+
request_info["headers"] = _filter_headers(dict(request.headers))
|
|
200
|
+
|
|
201
|
+
if should_send_default_pii():
|
|
202
|
+
request_info["env"] = {"REMOTE_ADDR": request.access_route[0]}
|
|
203
|
+
_add_user_to_event(event)
|
|
204
|
+
|
|
205
|
+
return event
|
|
206
|
+
|
|
207
|
+
return inner
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def _capture_exception(sender, exception, **kwargs):
|
|
211
|
+
# type: (Quart, Union[ValueError, BaseException], **Any) -> None
|
|
212
|
+
integration = sentry_sdk.get_client().get_integration(QuartIntegration)
|
|
213
|
+
if integration is None:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
event, hint = event_from_exception(
|
|
217
|
+
exception,
|
|
218
|
+
client_options=sentry_sdk.get_client().options,
|
|
219
|
+
mechanism={"type": "quart", "handled": False},
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _add_user_to_event(event):
|
|
226
|
+
# type: (Event) -> None
|
|
227
|
+
if quart_auth is None:
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
user = quart_auth.current_user
|
|
231
|
+
if user is None:
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
with capture_internal_exceptions():
|
|
235
|
+
user_info = event.setdefault("user", {})
|
|
236
|
+
|
|
237
|
+
user_info["id"] = quart_auth.current_user._auth_id
|