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.
- sentry_sdk/__init__.py +48 -30
- sentry_sdk/_compat.py +74 -61
- 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 +289 -0
- sentry_sdk/_types.py +338 -0
- 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 +496 -80
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +1023 -103
- sentry_sdk/consts.py +1438 -66
- 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 +15 -14
- sentry_sdk/envelope.py +369 -0
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +611 -280
- sentry_sdk/integrations/__init__.py +276 -49
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +180 -44
- sentry_sdk/integrations/aiohttp.py +291 -42
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +9 -8
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +341 -0
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +17 -10
- sentry_sdk/integrations/aws_lambda.py +377 -62
- sentry_sdk/integrations/beam.py +176 -0
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +221 -0
- 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 +134 -0
- 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 +48 -14
- sentry_sdk/integrations/django/__init__.py +584 -191
- sentry_sdk/integrations/django/asgi.py +245 -0
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +187 -0
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +79 -5
- sentry_sdk/integrations/django/transactions.py +49 -22
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +50 -13
- sentry_sdk/integrations/executing.py +67 -0
- sentry_sdk/integrations/falcon.py +272 -0
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +142 -88
- sentry_sdk/integrations/gcp.py +239 -0
- sentry_sdk/integrations/gnu_backtrace.py +99 -0
- 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 +307 -96
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +14 -31
- 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 +141 -0
- 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 +112 -68
- 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 +95 -37
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +294 -123
- sentry_sdk/integrations/serverless.py +48 -19
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +316 -0
- sentry_sdk/integrations/spark/spark_worker.py +116 -0
- sentry_sdk/integrations/sqlalchemy.py +142 -0
- 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 +235 -29
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +158 -28
- sentry_sdk/integrations/tornado.py +84 -52
- sentry_sdk/integrations/trytond.py +50 -0
- 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 +201 -119
- 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/py.typed +0 -0
- sentry_sdk/scope.py +1713 -85
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +405 -0
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +275 -0
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1486 -0
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +806 -134
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1625 -465
- sentry_sdk/worker.py +54 -25
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.7.5.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/integrations/celery.py +0 -119
- sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
- sentry_sdk-0.7.5.dist-info/METADATA +0 -36
- sentry_sdk-0.7.5.dist-info/RECORD +0 -39
- {sentry_sdk-0.7.5.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,31 +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
|
|
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
|
-
|
|
19
|
-
|
|
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:
|
|
28
|
+
from pyramid.response import Response
|
|
20
29
|
from typing import Any
|
|
21
30
|
from sentry_sdk.integrations.wsgi import _ScopedResponse
|
|
22
31
|
from typing import Callable
|
|
23
32
|
from typing import Dict
|
|
24
33
|
from typing import Optional
|
|
25
|
-
from webob.cookies import RequestCookies
|
|
26
|
-
from webob.
|
|
34
|
+
from webob.cookies import RequestCookies
|
|
35
|
+
from webob.request import _FieldStorageWithFile
|
|
27
36
|
|
|
28
37
|
from sentry_sdk.utils import ExcInfo
|
|
38
|
+
from sentry_sdk._types import Event, EventProcessor
|
|
29
39
|
|
|
30
40
|
|
|
31
41
|
if getattr(Request, "authenticated_userid", None):
|
|
@@ -34,20 +44,22 @@ if getattr(Request, "authenticated_userid", None):
|
|
|
34
44
|
# type: (Request) -> Optional[Any]
|
|
35
45
|
return request.authenticated_userid
|
|
36
46
|
|
|
37
|
-
|
|
38
47
|
else:
|
|
39
48
|
# bw-compat for pyramid < 1.5
|
|
40
49
|
from pyramid.security import authenticated_userid # type: ignore
|
|
41
50
|
|
|
42
51
|
|
|
52
|
+
TRANSACTION_STYLE_VALUES = ("route_name", "route_pattern")
|
|
53
|
+
|
|
54
|
+
|
|
43
55
|
class PyramidIntegration(Integration):
|
|
44
56
|
identifier = "pyramid"
|
|
57
|
+
origin = f"auto.http.{identifier}"
|
|
45
58
|
|
|
46
|
-
transaction_style =
|
|
59
|
+
transaction_style = ""
|
|
47
60
|
|
|
48
61
|
def __init__(self, transaction_style="route_name"):
|
|
49
62
|
# type: (str) -> None
|
|
50
|
-
TRANSACTION_STYLE_VALUES = ("route_name", "route_pattern")
|
|
51
63
|
if transaction_style not in TRANSACTION_STYLE_VALUES:
|
|
52
64
|
raise ValueError(
|
|
53
65
|
"Invalid value for transaction_style: %s (must be in %s)"
|
|
@@ -58,65 +70,105 @@ class PyramidIntegration(Integration):
|
|
|
58
70
|
@staticmethod
|
|
59
71
|
def setup_once():
|
|
60
72
|
# type: () -> None
|
|
61
|
-
from pyramid
|
|
73
|
+
from pyramid import router
|
|
62
74
|
|
|
63
|
-
|
|
75
|
+
old_call_view = router._call_view
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
@functools.wraps(old_call_view)
|
|
78
|
+
def sentry_patched_call_view(registry, request, *args, **kwargs):
|
|
66
79
|
# type: (Any, Request, *Any, **Any) -> Response
|
|
67
|
-
|
|
68
|
-
integration = hub.get_integration(PyramidIntegration)
|
|
80
|
+
integration = sentry_sdk.get_client().get_integration(PyramidIntegration)
|
|
69
81
|
if integration is None:
|
|
70
|
-
return
|
|
82
|
+
return old_call_view(registry, request, *args, **kwargs)
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
)
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
return old_handle_request(self, request, *args, **kwargs)
|
|
79
|
-
except Exception:
|
|
80
|
-
exc_info = sys.exc_info()
|
|
81
|
-
_capture_exception(exc_info)
|
|
82
|
-
reraise(*exc_info)
|
|
92
|
+
return old_call_view(registry, request, *args, **kwargs)
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
router._call_view = sentry_patched_call_view
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
if hasattr(Request, "invoke_exception_view"):
|
|
97
|
+
old_invoke_exception_view = Request.invoke_exception_view
|
|
87
98
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
def sentry_patched_invoke_exception_view(self, *args, **kwargs):
|
|
100
|
+
# type: (Request, *Any, **Any) -> Any
|
|
101
|
+
rv = old_invoke_exception_view(self, *args, **kwargs)
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
self.exc_info
|
|
105
|
+
and all(self.exc_info)
|
|
106
|
+
and rv.status_int == 500
|
|
107
|
+
and sentry_sdk.get_client().get_integration(PyramidIntegration)
|
|
108
|
+
is not None
|
|
109
|
+
):
|
|
110
|
+
_capture_exception(self.exc_info)
|
|
111
|
+
|
|
112
|
+
return rv
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
Request.invoke_exception_view = sentry_patched_invoke_exception_view
|
|
115
|
+
|
|
116
|
+
old_wsgi_call = router.Router.__call__
|
|
117
|
+
|
|
118
|
+
@ensure_integration_enabled(PyramidIntegration, old_wsgi_call)
|
|
119
|
+
def sentry_patched_wsgi_call(self, environ, start_response):
|
|
120
|
+
# type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
|
|
121
|
+
def sentry_patched_inner_wsgi_call(environ, start_response):
|
|
122
|
+
# type: (Dict[str, Any], Callable[..., Any]) -> Any
|
|
123
|
+
try:
|
|
124
|
+
return old_wsgi_call(self, environ, start_response)
|
|
125
|
+
except Exception:
|
|
126
|
+
einfo = sys.exc_info()
|
|
127
|
+
_capture_exception(einfo)
|
|
128
|
+
reraise(*einfo)
|
|
129
|
+
|
|
130
|
+
middleware = SentryWsgiMiddleware(
|
|
131
|
+
sentry_patched_inner_wsgi_call,
|
|
132
|
+
span_origin=PyramidIntegration.origin,
|
|
97
133
|
)
|
|
134
|
+
return middleware(environ, start_response)
|
|
98
135
|
|
|
99
|
-
Router.__call__ = sentry_patched_wsgi_call
|
|
136
|
+
router.Router.__call__ = sentry_patched_wsgi_call
|
|
100
137
|
|
|
101
138
|
|
|
102
|
-
|
|
103
|
-
|
|
139
|
+
@ensure_integration_enabled(PyramidIntegration)
|
|
140
|
+
def _capture_exception(exc_info):
|
|
141
|
+
# type: (ExcInfo) -> None
|
|
104
142
|
if exc_info[0] is None or issubclass(exc_info[0], HTTPException):
|
|
105
143
|
return
|
|
106
|
-
|
|
107
|
-
if hub.get_integration(PyramidIntegration) is None:
|
|
108
|
-
return
|
|
144
|
+
|
|
109
145
|
event, hint = event_from_exception(
|
|
110
146
|
exc_info,
|
|
111
|
-
client_options=
|
|
147
|
+
client_options=sentry_sdk.get_client().options,
|
|
112
148
|
mechanism={"type": "pyramid", "handled": False},
|
|
113
149
|
)
|
|
114
150
|
|
|
115
|
-
|
|
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
|
|
116
167
|
|
|
117
168
|
|
|
118
169
|
class PyramidRequestExtractor(RequestExtractor):
|
|
119
170
|
def url(self):
|
|
171
|
+
# type: () -> str
|
|
120
172
|
return self.request.path_url
|
|
121
173
|
|
|
122
174
|
def env(self):
|
|
@@ -140,7 +192,7 @@ class PyramidRequestExtractor(RequestExtractor):
|
|
|
140
192
|
}
|
|
141
193
|
|
|
142
194
|
def files(self):
|
|
143
|
-
# type: () -> Dict[str,
|
|
195
|
+
# type: () -> Dict[str, _FieldStorageWithFile]
|
|
144
196
|
return {
|
|
145
197
|
key: value
|
|
146
198
|
for key, value in self.request.POST.items()
|
|
@@ -148,7 +200,7 @@ class PyramidRequestExtractor(RequestExtractor):
|
|
|
148
200
|
}
|
|
149
201
|
|
|
150
202
|
def size_of_file(self, postdata):
|
|
151
|
-
# type: (
|
|
203
|
+
# type: (_FieldStorageWithFile) -> int
|
|
152
204
|
file = postdata.file
|
|
153
205
|
try:
|
|
154
206
|
return os.fstat(file.fileno()).st_size
|
|
@@ -157,29 +209,21 @@ class PyramidRequestExtractor(RequestExtractor):
|
|
|
157
209
|
|
|
158
210
|
|
|
159
211
|
def _make_event_processor(weak_request, integration):
|
|
160
|
-
# type: (Callable[[], Request], PyramidIntegration) ->
|
|
161
|
-
def
|
|
162
|
-
# type: (
|
|
212
|
+
# type: (Callable[[], Request], PyramidIntegration) -> EventProcessor
|
|
213
|
+
def pyramid_event_processor(event, hint):
|
|
214
|
+
# type: (Event, Dict[str, Any]) -> Event
|
|
163
215
|
request = weak_request()
|
|
164
216
|
if request is None:
|
|
165
217
|
return event
|
|
166
218
|
|
|
167
|
-
try:
|
|
168
|
-
if integration.transaction_style == "route_name":
|
|
169
|
-
event["transaction"] = request.matched_route.name
|
|
170
|
-
elif integration.transaction_style == "route_pattern":
|
|
171
|
-
event["transaction"] = request.matched_route.pattern
|
|
172
|
-
except Exception:
|
|
173
|
-
pass
|
|
174
|
-
|
|
175
219
|
with capture_internal_exceptions():
|
|
176
220
|
PyramidRequestExtractor(request).extract_into_event(event)
|
|
177
221
|
|
|
178
|
-
if
|
|
222
|
+
if should_send_default_pii():
|
|
179
223
|
with capture_internal_exceptions():
|
|
180
224
|
user_info = event.setdefault("user", {})
|
|
181
|
-
user_info
|
|
225
|
+
user_info.setdefault("id", authenticated_userid(request))
|
|
182
226
|
|
|
183
227
|
return event
|
|
184
228
|
|
|
185
|
-
return
|
|
229
|
+
return pyramid_event_processor
|