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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -30
  2. sentry_sdk/_compat.py +74 -61
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +289 -0
  8. sentry_sdk/_types.py +338 -0
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +496 -80
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +1023 -103
  16. sentry_sdk/consts.py +1438 -66
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +15 -14
  22. sentry_sdk/envelope.py +369 -0
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +611 -280
  25. sentry_sdk/integrations/__init__.py +276 -49
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +180 -44
  28. sentry_sdk/integrations/aiohttp.py +291 -42
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +9 -8
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +341 -0
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +17 -10
  37. sentry_sdk/integrations/aws_lambda.py +377 -62
  38. sentry_sdk/integrations/beam.py +176 -0
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +221 -0
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +134 -0
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +48 -14
  49. sentry_sdk/integrations/django/__init__.py +584 -191
  50. sentry_sdk/integrations/django/asgi.py +245 -0
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +187 -0
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +79 -5
  55. sentry_sdk/integrations/django/transactions.py +49 -22
  56. sentry_sdk/integrations/django/views.py +96 -0
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +50 -13
  59. sentry_sdk/integrations/executing.py +67 -0
  60. sentry_sdk/integrations/falcon.py +272 -0
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +142 -88
  63. sentry_sdk/integrations/gcp.py +239 -0
  64. sentry_sdk/integrations/gnu_backtrace.py +99 -0
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +307 -96
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +14 -31
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +141 -0
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +112 -68
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +95 -37
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +294 -123
  145. sentry_sdk/integrations/serverless.py +48 -19
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/__init__.py +4 -0
  148. sentry_sdk/integrations/spark/spark_driver.py +316 -0
  149. sentry_sdk/integrations/spark/spark_worker.py +116 -0
  150. sentry_sdk/integrations/sqlalchemy.py +142 -0
  151. sentry_sdk/integrations/starlette.py +737 -0
  152. sentry_sdk/integrations/starlite.py +292 -0
  153. sentry_sdk/integrations/statsig.py +37 -0
  154. sentry_sdk/integrations/stdlib.py +235 -29
  155. sentry_sdk/integrations/strawberry.py +394 -0
  156. sentry_sdk/integrations/sys_exit.py +70 -0
  157. sentry_sdk/integrations/threading.py +158 -28
  158. sentry_sdk/integrations/tornado.py +84 -52
  159. sentry_sdk/integrations/trytond.py +50 -0
  160. sentry_sdk/integrations/typer.py +60 -0
  161. sentry_sdk/integrations/unleash.py +33 -0
  162. sentry_sdk/integrations/unraisablehook.py +53 -0
  163. sentry_sdk/integrations/wsgi.py +201 -119
  164. sentry_sdk/logger.py +96 -0
  165. sentry_sdk/metrics.py +81 -0
  166. sentry_sdk/monitor.py +120 -0
  167. sentry_sdk/profiler/__init__.py +49 -0
  168. sentry_sdk/profiler/continuous_profiler.py +730 -0
  169. sentry_sdk/profiler/transaction_profiler.py +839 -0
  170. sentry_sdk/profiler/utils.py +195 -0
  171. sentry_sdk/py.typed +0 -0
  172. sentry_sdk/scope.py +1713 -85
  173. sentry_sdk/scrubber.py +177 -0
  174. sentry_sdk/serializer.py +405 -0
  175. sentry_sdk/session.py +177 -0
  176. sentry_sdk/sessions.py +275 -0
  177. sentry_sdk/spotlight.py +242 -0
  178. sentry_sdk/tracing.py +1486 -0
  179. sentry_sdk/tracing_utils.py +1236 -0
  180. sentry_sdk/transport.py +806 -134
  181. sentry_sdk/types.py +52 -0
  182. sentry_sdk/utils.py +1625 -465
  183. sentry_sdk/worker.py +54 -25
  184. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  185. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  186. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  187. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  188. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  189. sentry_sdk/integrations/celery.py +0 -119
  190. sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.7.5.dist-info/METADATA +0 -36
  192. sentry_sdk-0.7.5.dist-info/RECORD +0 -39
  193. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -1,142 +1,346 @@
1
1
  import sys
2
2
  import weakref
3
3
  from inspect import isawaitable
4
+ from urllib.parse import urlsplit
4
5
 
5
- from sentry_sdk._compat import urlparse, reraise
6
- from sentry_sdk.hub import Hub
7
- from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
8
- from sentry_sdk.integrations import Integration
6
+ import sentry_sdk
7
+ from sentry_sdk import continue_trace
8
+ from sentry_sdk.consts import OP
9
+ from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
9
10
  from sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers
10
11
  from sentry_sdk.integrations.logging import ignore_logger
11
-
12
- from sanic import Sanic # type: ignore
13
- from sanic.exceptions import SanicException # type: ignore
14
- from sanic.router import Router # type: ignore
15
- from sanic.handlers import ErrorHandler # type: ignore
16
-
17
- if False:
18
- from sanic.request import Request # type: ignore
19
-
12
+ from sentry_sdk.tracing import TransactionSource
13
+ from sentry_sdk.utils import (
14
+ capture_internal_exceptions,
15
+ ensure_integration_enabled,
16
+ event_from_exception,
17
+ HAS_REAL_CONTEXTVARS,
18
+ CONTEXTVARS_ERROR_MESSAGE,
19
+ parse_version,
20
+ reraise,
21
+ )
22
+
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Container
20
27
  from typing import Any
21
28
  from typing import Callable
22
- from typing import Dict
23
- from typing import List
24
- from typing import Tuple
25
- from sanic.exceptions import InvalidUsage
26
29
  from typing import Optional
27
30
  from typing import Union
28
- from sanic.request import RequestParameters
31
+ from typing import Dict
32
+
33
+ from sanic.request import Request, RequestParameters
34
+ from sanic.response import BaseHTTPResponse
35
+
36
+ from sentry_sdk._types import Event, EventProcessor, ExcInfo, Hint
37
+ from sanic.router import Route
38
+
39
+ try:
40
+ from sanic import Sanic, __version__ as SANIC_VERSION
41
+ from sanic.exceptions import SanicException
42
+ from sanic.router import Router
43
+ from sanic.handlers import ErrorHandler
44
+ except ImportError:
45
+ raise DidNotEnable("Sanic not installed")
46
+
47
+ old_error_handler_lookup = ErrorHandler.lookup
48
+ old_handle_request = Sanic.handle_request
49
+ old_router_get = Router.get
50
+
51
+ try:
52
+ # This method was introduced in Sanic v21.9
53
+ old_startup = Sanic._startup
54
+ except AttributeError:
55
+ pass
29
56
 
30
57
 
31
58
  class SanicIntegration(Integration):
32
59
  identifier = "sanic"
60
+ origin = f"auto.http.{identifier}"
61
+ version = None
62
+
63
+ def __init__(self, unsampled_statuses=frozenset({404})):
64
+ # type: (Optional[Container[int]]) -> None
65
+ """
66
+ The unsampled_statuses parameter can be used to specify for which HTTP statuses the
67
+ transactions should not be sent to Sentry. By default, transactions are sent for all
68
+ HTTP statuses, except 404. Set unsampled_statuses to None to send transactions for all
69
+ HTTP statuses, including 404.
70
+ """
71
+ self._unsampled_statuses = unsampled_statuses or set()
33
72
 
34
73
  @staticmethod
35
74
  def setup_once():
36
75
  # type: () -> None
37
- if sys.version_info < (3, 7):
38
- # Sanic is async. We better have contextvars or we're going to leak
39
- # state between requests.
40
- raise RuntimeError("The sanic integration for Sentry requires Python 3.7+")
76
+ SanicIntegration.version = parse_version(SANIC_VERSION)
77
+ _check_minimum_version(SanicIntegration, SanicIntegration.version)
78
+
79
+ if not HAS_REAL_CONTEXTVARS:
80
+ # We better have contextvars or we're going to leak state between
81
+ # requests.
82
+ raise DidNotEnable(
83
+ "The sanic integration for Sentry requires Python 3.7+ "
84
+ " or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE
85
+ )
41
86
 
42
- # Sanic 0.8 and older creates a logger named "root" and puts a
43
- # stringified version of every exception in there (without exc_info),
44
- # which our error deduplication can't detect.
45
- #
46
- # https://github.com/huge-success/sanic/issues/1332
47
- ignore_logger("root")
87
+ if SANIC_VERSION.startswith("0.8."):
88
+ # Sanic 0.8 and older creates a logger named "root" and puts a
89
+ # stringified version of every exception in there (without exc_info),
90
+ # which our error deduplication can't detect.
91
+ #
92
+ # We explicitly check the version here because it is a very
93
+ # invasive step to ignore this logger and not necessary in newer
94
+ # versions at all.
95
+ #
96
+ # https://github.com/huge-success/sanic/issues/1332
97
+ ignore_logger("root")
48
98
 
49
- old_handle_request = Sanic.handle_request
99
+ if SanicIntegration.version is not None and SanicIntegration.version < (21, 9):
100
+ _setup_legacy_sanic()
101
+ return
50
102
 
51
- async def sentry_handle_request(self, request, *args, **kwargs):
52
- # type: (Any, Request, *Any, **Any) -> Any
53
- hub = Hub.current
54
- if hub.get_integration(SanicIntegration) is None:
55
- return old_handle_request(self, request, *args, **kwargs)
103
+ _setup_sanic()
56
104
 
57
- weak_request = weakref.ref(request)
58
105
 
59
- with Hub(hub) as hub:
60
- with hub.configure_scope() as scope:
61
- scope.add_event_processor(_make_request_processor(weak_request))
106
+ class SanicRequestExtractor(RequestExtractor):
107
+ def content_length(self):
108
+ # type: () -> int
109
+ if self.request.body is None:
110
+ return 0
111
+ return len(self.request.body)
62
112
 
63
- response = old_handle_request(self, request, *args, **kwargs)
64
- if isawaitable(response):
65
- response = await response
113
+ def cookies(self):
114
+ # type: () -> Dict[str, str]
115
+ return dict(self.request.cookies)
66
116
 
67
- return response
117
+ def raw_data(self):
118
+ # type: () -> bytes
119
+ return self.request.body
68
120
 
69
- Sanic.handle_request = sentry_handle_request
121
+ def form(self):
122
+ # type: () -> RequestParameters
123
+ return self.request.form
124
+
125
+ def is_json(self):
126
+ # type: () -> bool
127
+ raise NotImplementedError()
128
+
129
+ def json(self):
130
+ # type: () -> Optional[Any]
131
+ return self.request.json
132
+
133
+ def files(self):
134
+ # type: () -> RequestParameters
135
+ return self.request.files
70
136
 
71
- old_router_get = Router.get
137
+ def size_of_file(self, file):
138
+ # type: (Any) -> int
139
+ return len(file.body or ())
72
140
 
73
- def sentry_router_get(self, request):
74
- # type: (Any, Request) -> Tuple[Callable, List, Dict[str, str], str]
75
- rv = old_router_get(self, request)
76
- hub = Hub.current
77
- if hub.get_integration(SanicIntegration) is not None:
78
- with capture_internal_exceptions():
79
- with hub.configure_scope() as scope:
80
- scope.transaction = rv[0].__name__
81
- return rv
82
141
 
83
- Router.get = sentry_router_get
142
+ def _setup_sanic():
143
+ # type: () -> None
144
+ Sanic._startup = _startup
145
+ ErrorHandler.lookup = _sentry_error_handler_lookup
84
146
 
85
- old_error_handler_lookup = ErrorHandler.lookup
86
147
 
87
- def sentry_error_handler_lookup(self, exception):
88
- # type: (Any, Union[ValueError, InvalidUsage]) -> Optional[Callable]
89
- _capture_exception(exception)
90
- old_error_handler = old_error_handler_lookup(self, exception)
148
+ def _setup_legacy_sanic():
149
+ # type: () -> None
150
+ Sanic.handle_request = _legacy_handle_request
151
+ Router.get = _legacy_router_get
152
+ ErrorHandler.lookup = _sentry_error_handler_lookup
91
153
 
92
- if old_error_handler is None:
93
- return None
94
154
 
95
- if Hub.current.get_integration(SanicIntegration) is None:
96
- return old_error_handler
155
+ async def _startup(self):
156
+ # type: (Sanic) -> None
157
+ # This happens about as early in the lifecycle as possible, just after the
158
+ # Request object is created. The body has not yet been consumed.
159
+ self.signal("http.lifecycle.request")(_context_enter)
97
160
 
98
- async def sentry_wrapped_error_handler(request, exception):
99
- # type: (Request, ValueError) -> Any
100
- try:
101
- response = old_error_handler(request, exception)
102
- if isawaitable(response):
103
- response = await response
104
- return response
105
- except Exception:
106
- exc_info = sys.exc_info()
107
- _capture_exception(exc_info)
108
- reraise(*exc_info)
161
+ # This happens after the handler is complete. In v21.9 this signal is not
162
+ # dispatched when there is an exception. Therefore we need to close out
163
+ # and call _context_exit from the custom exception handler as well.
164
+ # See https://github.com/sanic-org/sanic/issues/2297
165
+ self.signal("http.lifecycle.response")(_context_exit)
109
166
 
110
- return sentry_wrapped_error_handler
167
+ # This happens inside of request handling immediately after the route
168
+ # has been identified by the router.
169
+ self.signal("http.routing.after")(_set_transaction)
111
170
 
112
- ErrorHandler.lookup = sentry_error_handler_lookup
171
+ # The above signals need to be declared before this can be called.
172
+ await old_startup(self)
113
173
 
114
174
 
115
- def _capture_exception(
116
- exception # type: Union[Tuple[type, BaseException, Any], ValueError, InvalidUsage]
117
- ):
118
- # type: (...) -> None
119
- if isinstance(exception, SanicException):
120
- return
175
+ async def _context_enter(request):
176
+ # type: (Request) -> None
177
+ request.ctx._sentry_do_integration = (
178
+ sentry_sdk.get_client().get_integration(SanicIntegration) is not None
179
+ )
121
180
 
122
- hub = Hub.current
123
- integration = hub.get_integration(SanicIntegration)
124
- if integration is None:
181
+ if not request.ctx._sentry_do_integration:
125
182
  return
126
183
 
184
+ weak_request = weakref.ref(request)
185
+ request.ctx._sentry_scope = sentry_sdk.isolation_scope()
186
+ scope = request.ctx._sentry_scope.__enter__()
187
+ scope.clear_breadcrumbs()
188
+ scope.add_event_processor(_make_request_processor(weak_request))
189
+
190
+ transaction = continue_trace(
191
+ dict(request.headers),
192
+ op=OP.HTTP_SERVER,
193
+ # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction
194
+ name=request.path,
195
+ source=TransactionSource.URL,
196
+ origin=SanicIntegration.origin,
197
+ )
198
+ request.ctx._sentry_transaction = sentry_sdk.start_transaction(
199
+ transaction
200
+ ).__enter__()
201
+
202
+
203
+ async def _context_exit(request, response=None):
204
+ # type: (Request, Optional[BaseHTTPResponse]) -> None
205
+ with capture_internal_exceptions():
206
+ if not request.ctx._sentry_do_integration:
207
+ return
208
+
209
+ integration = sentry_sdk.get_client().get_integration(SanicIntegration)
210
+
211
+ response_status = None if response is None else response.status
212
+
213
+ # This capture_internal_exceptions block has been intentionally nested here, so that in case an exception
214
+ # happens while trying to end the transaction, we still attempt to exit the hub.
215
+ with capture_internal_exceptions():
216
+ request.ctx._sentry_transaction.set_http_status(response_status)
217
+ request.ctx._sentry_transaction.sampled &= (
218
+ isinstance(integration, SanicIntegration)
219
+ and response_status not in integration._unsampled_statuses
220
+ )
221
+ request.ctx._sentry_transaction.__exit__(None, None, None)
222
+
223
+ request.ctx._sentry_scope.__exit__(None, None, None)
224
+
225
+
226
+ async def _set_transaction(request, route, **_):
227
+ # type: (Request, Route, **Any) -> None
228
+ if request.ctx._sentry_do_integration:
229
+ with capture_internal_exceptions():
230
+ scope = sentry_sdk.get_current_scope()
231
+ route_name = route.name.replace(request.app.name, "").strip(".")
232
+ scope.set_transaction_name(route_name, source=TransactionSource.COMPONENT)
233
+
234
+
235
+ def _sentry_error_handler_lookup(self, exception, *args, **kwargs):
236
+ # type: (Any, Exception, *Any, **Any) -> Optional[object]
237
+ _capture_exception(exception)
238
+ old_error_handler = old_error_handler_lookup(self, exception, *args, **kwargs)
239
+
240
+ if old_error_handler is None:
241
+ return None
242
+
243
+ if sentry_sdk.get_client().get_integration(SanicIntegration) is None:
244
+ return old_error_handler
245
+
246
+ async def sentry_wrapped_error_handler(request, exception):
247
+ # type: (Request, Exception) -> Any
248
+ try:
249
+ response = old_error_handler(request, exception)
250
+ if isawaitable(response):
251
+ response = await response
252
+ return response
253
+ except Exception:
254
+ # Report errors that occur in Sanic error handler. These
255
+ # exceptions will not even show up in Sanic's
256
+ # `sanic.exceptions` logger.
257
+ exc_info = sys.exc_info()
258
+ _capture_exception(exc_info)
259
+ reraise(*exc_info)
260
+ finally:
261
+ # As mentioned in previous comment in _startup, this can be removed
262
+ # after https://github.com/sanic-org/sanic/issues/2297 is resolved
263
+ if SanicIntegration.version and SanicIntegration.version == (21, 9):
264
+ await _context_exit(request)
265
+
266
+ return sentry_wrapped_error_handler
267
+
268
+
269
+ async def _legacy_handle_request(self, request, *args, **kwargs):
270
+ # type: (Any, Request, *Any, **Any) -> Any
271
+ if sentry_sdk.get_client().get_integration(SanicIntegration) is None:
272
+ return await old_handle_request(self, request, *args, **kwargs)
273
+
274
+ weak_request = weakref.ref(request)
275
+
276
+ with sentry_sdk.isolation_scope() as scope:
277
+ scope.clear_breadcrumbs()
278
+ scope.add_event_processor(_make_request_processor(weak_request))
279
+
280
+ response = old_handle_request(self, request, *args, **kwargs)
281
+ if isawaitable(response):
282
+ response = await response
283
+
284
+ return response
285
+
286
+
287
+ def _legacy_router_get(self, *args):
288
+ # type: (Any, Union[Any, Request]) -> Any
289
+ rv = old_router_get(self, *args)
290
+ if sentry_sdk.get_client().get_integration(SanicIntegration) is not None:
291
+ with capture_internal_exceptions():
292
+ scope = sentry_sdk.get_isolation_scope()
293
+ if SanicIntegration.version and SanicIntegration.version >= (21, 3):
294
+ # Sanic versions above and including 21.3 append the app name to the
295
+ # route name, and so we need to remove it from Route name so the
296
+ # transaction name is consistent across all versions
297
+ sanic_app_name = self.ctx.app.name
298
+ sanic_route = rv[0].name
299
+
300
+ if sanic_route.startswith("%s." % sanic_app_name):
301
+ # We add a 1 to the len of the sanic_app_name because there is a dot
302
+ # that joins app name and the route name
303
+ # Format: app_name.route_name
304
+ sanic_route = sanic_route[len(sanic_app_name) + 1 :]
305
+
306
+ scope.set_transaction_name(
307
+ sanic_route, source=TransactionSource.COMPONENT
308
+ )
309
+ else:
310
+ scope.set_transaction_name(
311
+ rv[0].__name__, source=TransactionSource.COMPONENT
312
+ )
313
+
314
+ return rv
315
+
316
+
317
+ @ensure_integration_enabled(SanicIntegration)
318
+ def _capture_exception(exception):
319
+ # type: (Union[ExcInfo, BaseException]) -> None
127
320
  with capture_internal_exceptions():
128
321
  event, hint = event_from_exception(
129
322
  exception,
130
- client_options=hub.client.options,
323
+ client_options=sentry_sdk.get_client().options,
131
324
  mechanism={"type": "sanic", "handled": False},
132
325
  )
133
- hub.capture_event(event, hint=hint)
326
+
327
+ if hint and hasattr(hint["exc_info"][0], "quiet") and hint["exc_info"][0].quiet:
328
+ return
329
+
330
+ sentry_sdk.capture_event(event, hint=hint)
134
331
 
135
332
 
136
333
  def _make_request_processor(weak_request):
137
- # type: (Callable[[], Request]) -> Callable
334
+ # type: (Callable[[], Request]) -> EventProcessor
138
335
  def sanic_processor(event, hint):
139
- # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
336
+ # type: (Event, Optional[Hint]) -> Optional[Event]
337
+
338
+ try:
339
+ if hint and issubclass(hint["exc_info"][0], SanicException):
340
+ return None
341
+ except KeyError:
342
+ pass
343
+
140
344
  request = weak_request()
141
345
  if request is None:
142
346
  return event
@@ -146,7 +350,7 @@ def _make_request_processor(weak_request):
146
350
  extractor.extract_into_event(event)
147
351
 
148
352
  request_info = event["request"]
149
- urlparts = urlparse.urlsplit(request.url)
353
+ urlparts = urlsplit(request.url)
150
354
 
151
355
  request_info["url"] = "%s://%s%s" % (
152
356
  urlparts.scheme,
@@ -162,36 +366,3 @@ def _make_request_processor(weak_request):
162
366
  return event
163
367
 
164
368
  return sanic_processor
165
-
166
-
167
- class SanicRequestExtractor(RequestExtractor):
168
- def content_length(self):
169
- # type: () -> int
170
- if self.request.body is None:
171
- return 0
172
- return len(self.request.body)
173
-
174
- def cookies(self):
175
- return dict(self.request.cookies)
176
-
177
- def raw_data(self):
178
- # type: () -> bytes
179
- return self.request.body
180
-
181
- def form(self):
182
- # type: () -> RequestParameters
183
- return self.request.form
184
-
185
- def is_json(self):
186
- raise NotImplementedError()
187
-
188
- def json(self):
189
- # type: () -> Optional[Any]
190
- return self.request.json
191
-
192
- def files(self):
193
- # type: () -> RequestParameters
194
- return self.request.files
195
-
196
- def size_of_file(self, file):
197
- return len(file.body or ())
@@ -1,25 +1,59 @@
1
- import functools
2
1
  import sys
2
+ from functools import wraps
3
3
 
4
- from sentry_sdk import Hub
5
- from sentry_sdk.utils import event_from_exception
6
- from sentry_sdk._compat import reraise
4
+ import sentry_sdk
5
+ from sentry_sdk.utils import event_from_exception, reraise
7
6
 
7
+ from typing import TYPE_CHECKING
8
8
 
9
- def serverless_function(f=None, flush=True):
9
+ if TYPE_CHECKING:
10
+ from typing import Any
11
+ from typing import Callable
12
+ from typing import TypeVar
13
+ from typing import Union
14
+ from typing import Optional
15
+ from typing import overload
16
+
17
+ F = TypeVar("F", bound=Callable[..., Any])
18
+
19
+ else:
20
+
21
+ def overload(x):
22
+ # type: (F) -> F
23
+ return x
24
+
25
+
26
+ @overload
27
+ def serverless_function(f, flush=True):
28
+ # type: (F, bool) -> F
29
+ pass
30
+
31
+
32
+ @overload
33
+ def serverless_function(f=None, flush=True): # noqa: F811
34
+ # type: (None, bool) -> Callable[[F], F]
35
+ pass
36
+
37
+
38
+ def serverless_function(f=None, flush=True): # noqa
39
+ # type: (Optional[F], bool) -> Union[F, Callable[[F], F]]
10
40
  def wrapper(f):
11
- @functools.wraps(f)
41
+ # type: (F) -> F
42
+ @wraps(f)
12
43
  def inner(*args, **kwargs):
13
- with Hub(Hub.current):
44
+ # type: (*Any, **Any) -> Any
45
+ with sentry_sdk.isolation_scope() as scope:
46
+ scope.clear_breadcrumbs()
47
+
14
48
  try:
15
49
  return f(*args, **kwargs)
16
50
  except Exception:
17
51
  _capture_and_reraise()
18
52
  finally:
19
53
  if flush:
20
- _flush_client()
54
+ sentry_sdk.flush()
21
55
 
22
- return inner
56
+ return inner # type: ignore
23
57
 
24
58
  if f is None:
25
59
  return wrapper
@@ -28,20 +62,15 @@ def serverless_function(f=None, flush=True):
28
62
 
29
63
 
30
64
  def _capture_and_reraise():
65
+ # type: () -> None
31
66
  exc_info = sys.exc_info()
32
- hub = Hub.current
33
- if hub is not None and hub.client is not None:
67
+ client = sentry_sdk.get_client()
68
+ if client.is_active():
34
69
  event, hint = event_from_exception(
35
70
  exc_info,
36
- client_options=hub.client.options,
71
+ client_options=client.options,
37
72
  mechanism={"type": "serverless", "handled": False},
38
73
  )
39
- hub.capture_event(event, hint=hint)
74
+ sentry_sdk.capture_event(event, hint=hint)
40
75
 
41
76
  reraise(*exc_info)
42
-
43
-
44
- def _flush_client():
45
- hub = Hub.current
46
- if hub is not None:
47
- hub.flush()
@@ -0,0 +1,96 @@
1
+ import socket
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk._types import MYPY
5
+ from sentry_sdk.consts import OP
6
+ from sentry_sdk.integrations import Integration
7
+
8
+ if MYPY:
9
+ from socket import AddressFamily, SocketKind
10
+ from typing import Tuple, Optional, Union, List
11
+
12
+ __all__ = ["SocketIntegration"]
13
+
14
+
15
+ class SocketIntegration(Integration):
16
+ identifier = "socket"
17
+ origin = f"auto.socket.{identifier}"
18
+
19
+ @staticmethod
20
+ def setup_once():
21
+ # type: () -> None
22
+ """
23
+ patches two of the most used functions of socket: create_connection and getaddrinfo(dns resolver)
24
+ """
25
+ _patch_create_connection()
26
+ _patch_getaddrinfo()
27
+
28
+
29
+ def _get_span_description(host, port):
30
+ # type: (Union[bytes, str, None], Union[bytes, str, int, None]) -> str
31
+
32
+ try:
33
+ host = host.decode() # type: ignore
34
+ except (UnicodeDecodeError, AttributeError):
35
+ pass
36
+
37
+ try:
38
+ port = port.decode() # type: ignore
39
+ except (UnicodeDecodeError, AttributeError):
40
+ pass
41
+
42
+ description = "%s:%s" % (host, port) # type: ignore
43
+ return description
44
+
45
+
46
+ def _patch_create_connection():
47
+ # type: () -> None
48
+ real_create_connection = socket.create_connection
49
+
50
+ def create_connection(
51
+ address,
52
+ timeout=socket._GLOBAL_DEFAULT_TIMEOUT, # type: ignore
53
+ source_address=None,
54
+ ):
55
+ # type: (Tuple[Optional[str], int], Optional[float], Optional[Tuple[Union[bytearray, bytes, str], int]])-> socket.socket
56
+ integration = sentry_sdk.get_client().get_integration(SocketIntegration)
57
+ if integration is None:
58
+ return real_create_connection(address, timeout, source_address)
59
+
60
+ with sentry_sdk.start_span(
61
+ op=OP.SOCKET_CONNECTION,
62
+ name=_get_span_description(address[0], address[1]),
63
+ origin=SocketIntegration.origin,
64
+ ) as span:
65
+ span.set_data("address", address)
66
+ span.set_data("timeout", timeout)
67
+ span.set_data("source_address", source_address)
68
+
69
+ return real_create_connection(
70
+ address=address, timeout=timeout, source_address=source_address
71
+ )
72
+
73
+ socket.create_connection = create_connection # type: ignore
74
+
75
+
76
+ def _patch_getaddrinfo():
77
+ # type: () -> None
78
+ real_getaddrinfo = socket.getaddrinfo
79
+
80
+ def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
81
+ # type: (Union[bytes, str, None], Union[bytes, str, int, None], int, int, int, int) -> List[Tuple[AddressFamily, SocketKind, int, str, Union[Tuple[str, int], Tuple[str, int, int, int], Tuple[int, bytes]]]]
82
+ integration = sentry_sdk.get_client().get_integration(SocketIntegration)
83
+ if integration is None:
84
+ return real_getaddrinfo(host, port, family, type, proto, flags)
85
+
86
+ with sentry_sdk.start_span(
87
+ op=OP.SOCKET_DNS,
88
+ name=_get_span_description(host, port),
89
+ origin=SocketIntegration.origin,
90
+ ) as span:
91
+ span.set_data("host", host)
92
+ span.set_data("port", port)
93
+
94
+ return real_getaddrinfo(host, port, family, type, proto, flags)
95
+
96
+ socket.getaddrinfo = getaddrinfo
@@ -0,0 +1,4 @@
1
+ from sentry_sdk.integrations.spark.spark_driver import SparkIntegration
2
+ from sentry_sdk.integrations.spark.spark_worker import SparkWorkerIntegration
3
+
4
+ __all__ = ["SparkIntegration", "SparkWorkerIntegration"]