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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,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
- from __future__ import absolute_import
2
-
1
+ import functools
3
2
  import os
4
3
  import sys
5
4
  import weakref
6
5
 
7
- from pyramid.httpexceptions import HTTPException
8
- from pyramid.request import Request
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._types import MYPY
19
-
20
- if MYPY:
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 # type: ignore
28
- from webob.compat import cgi_FieldStorage # type: ignore
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 = None
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
- hub = Hub.current
74
- integration = hub.get_integration(PyramidIntegration)
75
-
76
- if integration is not None:
77
- with hub.configure_scope() as scope:
78
- try:
79
- if integration.transaction_style == "route_name":
80
- scope.transaction = request.matched_route.name
81
- elif integration.transaction_style == "route_pattern":
82
- scope.transaction = request.matched_route.pattern
83
- except Exception:
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 Hub.current.get_integration(PyramidIntegration) is not None
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
- return SentryWsgiMiddleware(sentry_patched_inner_wsgi_call)(
132
- environ, start_response
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=client.options,
147
+ client_options=sentry_sdk.get_client().options,
152
148
  mechanism={"type": "pyramid", "handled": False},
153
149
  )
154
150
 
155
- hub.capture_event(event, hint=hint)
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 iteritems(self.request.POST)
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, cgi_FieldStorage]
195
+ # type: () -> Dict[str, _FieldStorageWithFile]
185
196
  return {
186
197
  key: value
187
- for key, value in iteritems(self.request.POST)
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: (cgi_FieldStorage) -> int
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 event_processor(event, hint):
203
- # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
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 _should_send_default_pii():
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 event_processor
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