sentry-sdk 3.0.0a5__py2.py3-none-any.whl → 3.0.0a6__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.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- sentry_sdk/ai/utils.py +7 -8
- sentry_sdk/api.py +13 -2
- sentry_sdk/client.py +93 -17
- sentry_sdk/consts.py +14 -6
- sentry_sdk/crons/api.py +5 -0
- sentry_sdk/integrations/anthropic.py +133 -73
- sentry_sdk/integrations/asgi.py +10 -9
- sentry_sdk/integrations/asyncio.py +85 -20
- sentry_sdk/integrations/clickhouse_driver.py +55 -28
- sentry_sdk/integrations/fastapi.py +1 -7
- sentry_sdk/integrations/gnu_backtrace.py +6 -3
- sentry_sdk/integrations/langchain.py +462 -218
- sentry_sdk/integrations/litestar.py +1 -1
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +0 -2
- sentry_sdk/integrations/openai_agents/patches/runner.py +18 -15
- sentry_sdk/integrations/quart.py +1 -1
- sentry_sdk/integrations/starlette.py +1 -5
- sentry_sdk/integrations/starlite.py +2 -2
- sentry_sdk/scope.py +11 -11
- sentry_sdk/tracing.py +94 -17
- sentry_sdk/tracing_utils.py +330 -33
- sentry_sdk/transport.py +357 -62
- sentry_sdk/utils.py +23 -5
- sentry_sdk/worker.py +197 -3
- {sentry_sdk-3.0.0a5.dist-info → sentry_sdk-3.0.0a6.dist-info}/METADATA +3 -1
- {sentry_sdk-3.0.0a5.dist-info → sentry_sdk-3.0.0a6.dist-info}/RECORD +30 -30
- {sentry_sdk-3.0.0a5.dist-info → sentry_sdk-3.0.0a6.dist-info}/WHEEL +0 -0
- {sentry_sdk-3.0.0a5.dist-info → sentry_sdk-3.0.0a6.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a5.dist-info → sentry_sdk-3.0.0a6.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a5.dist-info → sentry_sdk-3.0.0a6.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,13 @@ import sys
|
|
|
4
4
|
import sentry_sdk
|
|
5
5
|
from sentry_sdk.consts import OP
|
|
6
6
|
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
7
|
-
from sentry_sdk.utils import
|
|
7
|
+
from sentry_sdk.utils import (
|
|
8
|
+
event_from_exception,
|
|
9
|
+
logger,
|
|
10
|
+
reraise,
|
|
11
|
+
is_internal_task,
|
|
12
|
+
)
|
|
13
|
+
from sentry_sdk.transport import AsyncHttpTransport
|
|
8
14
|
|
|
9
15
|
try:
|
|
10
16
|
import asyncio
|
|
@@ -29,6 +35,72 @@ def get_name(coro: Any) -> str:
|
|
|
29
35
|
)
|
|
30
36
|
|
|
31
37
|
|
|
38
|
+
def patch_loop_close() -> None:
|
|
39
|
+
"""Patch loop.close to flush pending events before shutdown."""
|
|
40
|
+
# Atexit shutdown hook happens after the event loop is closed.
|
|
41
|
+
# Therefore, it is necessary to patch the loop.close method to ensure
|
|
42
|
+
# that pending events are flushed before the interpreter shuts down.
|
|
43
|
+
try:
|
|
44
|
+
loop = asyncio.get_running_loop()
|
|
45
|
+
except RuntimeError:
|
|
46
|
+
# No running loop → cannot patch now
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if getattr(loop, "_sentry_flush_patched", False):
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
async def _flush() -> None:
|
|
53
|
+
client = sentry_sdk.get_client()
|
|
54
|
+
if not client:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
if not isinstance(client.transport, AsyncHttpTransport):
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
await client.close_async()
|
|
62
|
+
except Exception:
|
|
63
|
+
logger.warning("Sentry flush failed during loop shutdown", exc_info=True)
|
|
64
|
+
|
|
65
|
+
orig_close = loop.close
|
|
66
|
+
|
|
67
|
+
def _patched_close() -> None:
|
|
68
|
+
try:
|
|
69
|
+
loop.run_until_complete(_flush())
|
|
70
|
+
finally:
|
|
71
|
+
orig_close()
|
|
72
|
+
|
|
73
|
+
loop.close = _patched_close # type: ignore
|
|
74
|
+
loop._sentry_flush_patched = True # type: ignore
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _create_task_with_factory(
|
|
78
|
+
orig_task_factory: Any,
|
|
79
|
+
loop: asyncio.AbstractEventLoop,
|
|
80
|
+
coro: Coroutine[Any, Any, Any],
|
|
81
|
+
**kwargs: Any,
|
|
82
|
+
) -> asyncio.Task[Any]:
|
|
83
|
+
task = None
|
|
84
|
+
|
|
85
|
+
# Trying to use user set task factory (if there is one)
|
|
86
|
+
if orig_task_factory:
|
|
87
|
+
task = orig_task_factory(loop, coro, **kwargs)
|
|
88
|
+
|
|
89
|
+
if task is None:
|
|
90
|
+
# The default task factory in `asyncio` does not have its own function
|
|
91
|
+
# but is just a couple of lines in `asyncio.base_events.create_task()`
|
|
92
|
+
# Those lines are copied here.
|
|
93
|
+
|
|
94
|
+
# WARNING:
|
|
95
|
+
# If the default behavior of the task creation in asyncio changes,
|
|
96
|
+
# this will break!
|
|
97
|
+
task = Task(coro, loop=loop, **kwargs)
|
|
98
|
+
if task._source_traceback: # type: ignore
|
|
99
|
+
del task._source_traceback[-1] # type: ignore
|
|
100
|
+
|
|
101
|
+
return task
|
|
102
|
+
|
|
103
|
+
|
|
32
104
|
def patch_asyncio() -> None:
|
|
33
105
|
orig_task_factory = None
|
|
34
106
|
try:
|
|
@@ -41,6 +113,14 @@ def patch_asyncio() -> None:
|
|
|
41
113
|
**kwargs: Any,
|
|
42
114
|
) -> asyncio.Future[Any]:
|
|
43
115
|
|
|
116
|
+
# Check if this is an internal Sentry task
|
|
117
|
+
is_internal = is_internal_task()
|
|
118
|
+
|
|
119
|
+
if is_internal:
|
|
120
|
+
return _create_task_with_factory(
|
|
121
|
+
orig_task_factory, loop, coro, **kwargs
|
|
122
|
+
)
|
|
123
|
+
|
|
44
124
|
async def _task_with_sentry_span_creation() -> Any:
|
|
45
125
|
result = None
|
|
46
126
|
|
|
@@ -58,25 +138,9 @@ def patch_asyncio() -> None:
|
|
|
58
138
|
|
|
59
139
|
return result
|
|
60
140
|
|
|
61
|
-
task =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if orig_task_factory:
|
|
65
|
-
task = orig_task_factory(
|
|
66
|
-
loop, _task_with_sentry_span_creation(), **kwargs
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
if task is None:
|
|
70
|
-
# The default task factory in `asyncio` does not have its own function
|
|
71
|
-
# but is just a couple of lines in `asyncio.base_events.create_task()`
|
|
72
|
-
# Those lines are copied here.
|
|
73
|
-
|
|
74
|
-
# WARNING:
|
|
75
|
-
# If the default behavior of the task creation in asyncio changes,
|
|
76
|
-
# this will break!
|
|
77
|
-
task = Task(_task_with_sentry_span_creation(), loop=loop, **kwargs)
|
|
78
|
-
if task._source_traceback: # type: ignore
|
|
79
|
-
del task._source_traceback[-1] # type: ignore
|
|
141
|
+
task = _create_task_with_factory(
|
|
142
|
+
orig_task_factory, loop, _task_with_sentry_span_creation(), **kwargs
|
|
143
|
+
)
|
|
80
144
|
|
|
81
145
|
# Set the task name to include the original coroutine's name
|
|
82
146
|
try:
|
|
@@ -124,3 +188,4 @@ class AsyncioIntegration(Integration):
|
|
|
124
188
|
@staticmethod
|
|
125
189
|
def setup_once() -> None:
|
|
126
190
|
patch_asyncio()
|
|
191
|
+
patch_loop_close()
|
|
@@ -13,7 +13,8 @@ from sentry_sdk.utils import (
|
|
|
13
13
|
from typing import TYPE_CHECKING
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from
|
|
16
|
+
from collections.abc import Iterator
|
|
17
|
+
from typing import Any, ParamSpec, Callable, TypeVar
|
|
17
18
|
|
|
18
19
|
P = ParamSpec("P")
|
|
19
20
|
T = TypeVar("T")
|
|
@@ -40,9 +41,7 @@ class ClickhouseDriverIntegration(Integration):
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
# If the query contains parameters then the send_data function is used to send those parameters to clickhouse
|
|
43
|
-
|
|
44
|
-
clickhouse_driver.client.Client.send_data
|
|
45
|
-
)
|
|
44
|
+
_wrap_send_data()
|
|
46
45
|
|
|
47
46
|
# Every query ends either with the Client's `receive_end_of_query` (no result expected)
|
|
48
47
|
# or its `receive_result` (result expected)
|
|
@@ -134,36 +133,64 @@ def _wrap_end(f: Callable[P, T]) -> Callable[P, T]:
|
|
|
134
133
|
return _inner_end
|
|
135
134
|
|
|
136
135
|
|
|
137
|
-
def _wrap_send_data(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
def _wrap_send_data() -> None:
|
|
137
|
+
original_send_data = clickhouse_driver.client.Client.send_data
|
|
138
|
+
|
|
139
|
+
def _inner_send_data(
|
|
140
|
+
self: clickhouse_driver.client.Client,
|
|
141
|
+
sample_block: Any,
|
|
142
|
+
data: Any,
|
|
143
|
+
types_check: bool = False,
|
|
144
|
+
columnar: bool = False,
|
|
145
|
+
*args: Any,
|
|
146
|
+
**kwargs: Any,
|
|
147
|
+
) -> Any:
|
|
148
|
+
span = getattr(self.connection, "_sentry_span", None)
|
|
149
|
+
if span is None:
|
|
150
|
+
return original_send_data(
|
|
151
|
+
self, sample_block, data, types_check, columnar, *args, **kwargs
|
|
152
|
+
)
|
|
142
153
|
|
|
143
|
-
|
|
144
|
-
span
|
|
154
|
+
db_data = _get_db_data(self.connection)
|
|
155
|
+
_set_on_span(span, db_data)
|
|
145
156
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
saved_db_data: dict[str, Any] = getattr(self.connection, "_sentry_db_data", {})
|
|
158
|
+
db_params: list[Any] = saved_db_data.get("db.params") or []
|
|
159
|
+
|
|
160
|
+
if should_send_default_pii():
|
|
161
|
+
if isinstance(data, (list, tuple)):
|
|
162
|
+
db_params.extend(data)
|
|
163
|
+
|
|
164
|
+
else: # data is a generic iterator
|
|
165
|
+
orig_data = data
|
|
166
|
+
|
|
167
|
+
# Wrap the generator to add items to db.params as they are yielded.
|
|
168
|
+
# This allows us to send the params to Sentry without needing to allocate
|
|
169
|
+
# memory for the entire generator at once.
|
|
170
|
+
def wrapped_generator() -> "Iterator[Any]":
|
|
171
|
+
for item in orig_data:
|
|
172
|
+
db_params.append(item)
|
|
173
|
+
yield item
|
|
174
|
+
|
|
175
|
+
# Replace the original iterator with the wrapped one.
|
|
176
|
+
data = wrapped_generator()
|
|
177
|
+
|
|
178
|
+
rv = original_send_data(
|
|
179
|
+
self, sample_block, data, types_check, columnar, *args, **kwargs
|
|
180
|
+
)
|
|
149
181
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
db_params_data = args[2]
|
|
156
|
-
if isinstance(db_params_data, list):
|
|
157
|
-
db_params.extend(db_params_data)
|
|
158
|
-
saved_db_data["db.params"] = db_params
|
|
159
|
-
span.set_attribute("db.params", _serialize_span_attribute(db_params))
|
|
182
|
+
if should_send_default_pii() and db_params:
|
|
183
|
+
# need to do this after the original function call to make sure
|
|
184
|
+
# db_params is populated correctly
|
|
185
|
+
saved_db_data["db.params"] = db_params
|
|
186
|
+
span.set_attribute("db.params", _serialize_span_attribute(db_params))
|
|
160
187
|
|
|
161
|
-
return
|
|
188
|
+
return rv
|
|
162
189
|
|
|
163
|
-
|
|
190
|
+
clickhouse_driver.client.Client.send_data = _inner_send_data
|
|
164
191
|
|
|
165
192
|
|
|
166
|
-
def _get_db_data(connection: clickhouse_driver.connection.Connection) ->
|
|
193
|
+
def _get_db_data(connection: clickhouse_driver.connection.Connection) -> dict[str, str]:
|
|
167
194
|
return {
|
|
168
195
|
SPANDATA.DB_SYSTEM: "clickhouse",
|
|
169
196
|
SPANDATA.SERVER_ADDRESS: connection.host,
|
|
@@ -173,6 +200,6 @@ def _get_db_data(connection: clickhouse_driver.connection.Connection) -> Dict[st
|
|
|
173
200
|
}
|
|
174
201
|
|
|
175
202
|
|
|
176
|
-
def _set_on_span(span: Span, data:
|
|
203
|
+
def _set_on_span(span: Span, data: dict[str, Any]) -> None:
|
|
177
204
|
for key, value in data.items():
|
|
178
205
|
span.set_attribute(key, _serialize_span_attribute(value))
|
|
@@ -7,10 +7,7 @@ import sentry_sdk
|
|
|
7
7
|
from sentry_sdk.consts import SOURCE_FOR_STYLE, TransactionSource
|
|
8
8
|
from sentry_sdk.integrations import DidNotEnable
|
|
9
9
|
from sentry_sdk.scope import should_send_default_pii
|
|
10
|
-
from sentry_sdk.utils import
|
|
11
|
-
transaction_from_function,
|
|
12
|
-
logger,
|
|
13
|
-
)
|
|
10
|
+
from sentry_sdk.utils import transaction_from_function
|
|
14
11
|
|
|
15
12
|
from typing import TYPE_CHECKING
|
|
16
13
|
|
|
@@ -67,9 +64,6 @@ def _set_transaction_name_and_source(
|
|
|
67
64
|
source = SOURCE_FOR_STYLE[transaction_style]
|
|
68
65
|
|
|
69
66
|
scope.set_transaction_name(name, source=source)
|
|
70
|
-
logger.debug(
|
|
71
|
-
"[FastAPI] Set transaction name and source on scope: %s / %s", name, source
|
|
72
|
-
)
|
|
73
67
|
|
|
74
68
|
|
|
75
69
|
def patch_get_request_handler() -> None:
|
|
@@ -12,13 +12,16 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from typing import Any
|
|
13
13
|
from sentry_sdk._types import Event
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
# function is everything between index at @
|
|
16
|
+
# and then we match on the @ plus the hex val
|
|
17
|
+
FUNCTION_RE = r"[^@]+?"
|
|
18
|
+
HEX_ADDRESS = r"\s+@\s+0x[0-9a-fA-F]+"
|
|
17
19
|
|
|
18
20
|
FRAME_RE = r"""
|
|
19
|
-
^(?P<index>\d+)\.\s+(?P<function>{FUNCTION_RE}
|
|
21
|
+
^(?P<index>\d+)\.\s+(?P<function>{FUNCTION_RE}){HEX_ADDRESS}(?:\s+in\s+(?P<package>.+))?$
|
|
20
22
|
""".format(
|
|
21
23
|
FUNCTION_RE=FUNCTION_RE,
|
|
24
|
+
HEX_ADDRESS=HEX_ADDRESS,
|
|
22
25
|
)
|
|
23
26
|
|
|
24
27
|
FRAME_RE = re.compile(FRAME_RE, re.MULTILINE | re.VERBOSE)
|