sentry-sdk 2.38.0__py2.py3-none-any.whl → 2.40.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.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- sentry_sdk/client.py +6 -6
- sentry_sdk/consts.py +15 -3
- sentry_sdk/envelope.py +28 -14
- sentry_sdk/feature_flags.py +0 -1
- sentry_sdk/hub.py +17 -9
- sentry_sdk/integrations/__init__.py +2 -0
- sentry_sdk/integrations/anthropic.py +18 -3
- sentry_sdk/integrations/asgi.py +3 -2
- sentry_sdk/integrations/cohere.py +4 -0
- sentry_sdk/integrations/dedupe.py +16 -2
- sentry_sdk/integrations/dramatiq.py +89 -31
- sentry_sdk/integrations/grpc/aio/client.py +2 -1
- sentry_sdk/integrations/grpc/client.py +3 -4
- sentry_sdk/integrations/huggingface_hub.py +3 -2
- sentry_sdk/integrations/langchain.py +12 -12
- sentry_sdk/integrations/launchdarkly.py +0 -1
- sentry_sdk/integrations/litellm.py +251 -0
- sentry_sdk/integrations/litestar.py +4 -4
- sentry_sdk/integrations/openai.py +13 -8
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +4 -1
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +1 -1
- sentry_sdk/integrations/openai_agents/utils.py +28 -1
- sentry_sdk/integrations/pure_eval.py +3 -1
- sentry_sdk/integrations/spark/spark_driver.py +2 -1
- sentry_sdk/integrations/sqlalchemy.py +2 -6
- sentry_sdk/integrations/starlette.py +1 -3
- sentry_sdk/integrations/starlite.py +4 -4
- sentry_sdk/integrations/wsgi.py +3 -2
- sentry_sdk/metrics.py +17 -11
- sentry_sdk/profiler/utils.py +2 -6
- sentry_sdk/scope.py +6 -3
- sentry_sdk/serializer.py +13 -4
- sentry_sdk/session.py +4 -2
- sentry_sdk/sessions.py +4 -2
- sentry_sdk/tracing.py +38 -8
- sentry_sdk/tracing_utils.py +15 -4
- sentry_sdk/transport.py +8 -9
- sentry_sdk/utils.py +5 -3
- {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/METADATA +3 -1
- {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/RECORD +44 -43
- {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/WHEEL +0 -0
- {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/top_level.txt +0 -0
sentry_sdk/client.py
CHANGED
|
@@ -178,9 +178,7 @@ class BaseClient:
|
|
|
178
178
|
|
|
179
179
|
def __init__(self, options=None):
|
|
180
180
|
# type: (Optional[Dict[str, Any]]) -> None
|
|
181
|
-
self.options =
|
|
182
|
-
options if options is not None else DEFAULT_OPTIONS
|
|
183
|
-
) # type: Dict[str, Any]
|
|
181
|
+
self.options = options if options is not None else DEFAULT_OPTIONS # type: Dict[str, Any]
|
|
184
182
|
|
|
185
183
|
self.transport = None # type: Optional[Transport]
|
|
186
184
|
self.monitor = None # type: Optional[Monitor]
|
|
@@ -956,7 +954,7 @@ class _Client(BaseClient):
|
|
|
956
954
|
debug = self.options.get("debug", False)
|
|
957
955
|
if debug:
|
|
958
956
|
logger.debug(
|
|
959
|
-
f
|
|
957
|
+
f"[Sentry Logs] [{log.get('severity_text')}] {log.get('body')}"
|
|
960
958
|
)
|
|
961
959
|
|
|
962
960
|
before_send_log = get_before_send_log(self.options)
|
|
@@ -970,7 +968,8 @@ class _Client(BaseClient):
|
|
|
970
968
|
self.log_batcher.add(log)
|
|
971
969
|
|
|
972
970
|
def capture_session(
|
|
973
|
-
self,
|
|
971
|
+
self,
|
|
972
|
+
session, # type: Session
|
|
974
973
|
):
|
|
975
974
|
# type: (...) -> None
|
|
976
975
|
if not session.release:
|
|
@@ -991,7 +990,8 @@ class _Client(BaseClient):
|
|
|
991
990
|
...
|
|
992
991
|
|
|
993
992
|
def get_integration(
|
|
994
|
-
self,
|
|
993
|
+
self,
|
|
994
|
+
name_or_class, # type: Union[str, Type[Integration]]
|
|
995
995
|
):
|
|
996
996
|
# type: (...) -> Optional[Integration]
|
|
997
997
|
"""Returns the integration for this client by name or class.
|
sentry_sdk/consts.py
CHANGED
|
@@ -40,6 +40,7 @@ if TYPE_CHECKING:
|
|
|
40
40
|
from typing import Any
|
|
41
41
|
from typing import Sequence
|
|
42
42
|
from typing import Tuple
|
|
43
|
+
from typing import AbstractSet
|
|
43
44
|
from typing_extensions import Literal
|
|
44
45
|
from typing_extensions import TypedDict
|
|
45
46
|
|
|
@@ -765,11 +766,12 @@ class SPANSTATUS:
|
|
|
765
766
|
CANCELLED = "cancelled"
|
|
766
767
|
DATA_LOSS = "data_loss"
|
|
767
768
|
DEADLINE_EXCEEDED = "deadline_exceeded"
|
|
769
|
+
ERROR = "error" # OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
|
|
768
770
|
FAILED_PRECONDITION = "failed_precondition"
|
|
769
771
|
INTERNAL_ERROR = "internal_error"
|
|
770
772
|
INVALID_ARGUMENT = "invalid_argument"
|
|
771
773
|
NOT_FOUND = "not_found"
|
|
772
|
-
OK = "ok"
|
|
774
|
+
OK = "ok" # HTTP 200 and OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
|
|
773
775
|
OUT_OF_RANGE = "out_of_range"
|
|
774
776
|
PERMISSION_DENIED = "permission_denied"
|
|
775
777
|
RESOURCE_EXHAUSTED = "resource_exhausted"
|
|
@@ -777,6 +779,7 @@ class SPANSTATUS:
|
|
|
777
779
|
UNAVAILABLE = "unavailable"
|
|
778
780
|
UNIMPLEMENTED = "unimplemented"
|
|
779
781
|
UNKNOWN_ERROR = "unknown_error"
|
|
782
|
+
UNSET = "unset" # OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
|
|
780
783
|
|
|
781
784
|
|
|
782
785
|
class OP:
|
|
@@ -836,6 +839,7 @@ class OP:
|
|
|
836
839
|
QUEUE_TASK_HUEY = "queue.task.huey"
|
|
837
840
|
QUEUE_SUBMIT_RAY = "queue.submit.ray"
|
|
838
841
|
QUEUE_TASK_RAY = "queue.task.ray"
|
|
842
|
+
QUEUE_TASK_DRAMATIQ = "queue.task.dramatiq"
|
|
839
843
|
SUBPROCESS = "subprocess"
|
|
840
844
|
SUBPROCESS_WAIT = "subprocess.wait"
|
|
841
845
|
SUBPROCESS_COMMUNICATE = "subprocess.communicate"
|
|
@@ -850,7 +854,6 @@ class OP:
|
|
|
850
854
|
# This type exists to trick mypy and PyCharm into thinking `init` and `Client`
|
|
851
855
|
# take these arguments (even though they take opaque **kwargs)
|
|
852
856
|
class ClientConstructor:
|
|
853
|
-
|
|
854
857
|
def __init__(
|
|
855
858
|
self,
|
|
856
859
|
dsn=None, # type: Optional[str]
|
|
@@ -918,6 +921,7 @@ class ClientConstructor:
|
|
|
918
921
|
max_stack_frames=DEFAULT_MAX_STACK_FRAMES, # type: Optional[int]
|
|
919
922
|
enable_logs=False, # type: bool
|
|
920
923
|
before_send_log=None, # type: Optional[Callable[[Log, Hint], Optional[Log]]]
|
|
924
|
+
trace_ignore_status_codes=frozenset(), # type: AbstractSet[int]
|
|
921
925
|
):
|
|
922
926
|
# type: (...) -> None
|
|
923
927
|
"""Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`.
|
|
@@ -1306,6 +1310,14 @@ class ClientConstructor:
|
|
|
1306
1310
|
function will be retained. If the function returns None, the log will
|
|
1307
1311
|
not be sent to Sentry.
|
|
1308
1312
|
|
|
1313
|
+
:param trace_ignore_status_codes: An optional property that disables tracing for
|
|
1314
|
+
HTTP requests with certain status codes.
|
|
1315
|
+
|
|
1316
|
+
Requests are not traced if the status code is contained in the provided set.
|
|
1317
|
+
|
|
1318
|
+
If `trace_ignore_status_codes` is not provided, requests with any status code
|
|
1319
|
+
may be traced.
|
|
1320
|
+
|
|
1309
1321
|
:param _experiments:
|
|
1310
1322
|
"""
|
|
1311
1323
|
pass
|
|
@@ -1331,4 +1343,4 @@ DEFAULT_OPTIONS = _get_default_options()
|
|
|
1331
1343
|
del _get_default_options
|
|
1332
1344
|
|
|
1333
1345
|
|
|
1334
|
-
VERSION = "2.
|
|
1346
|
+
VERSION = "2.40.0"
|
sentry_sdk/envelope.py
CHANGED
|
@@ -57,25 +57,29 @@ class Envelope:
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
def add_event(
|
|
60
|
-
self,
|
|
60
|
+
self,
|
|
61
|
+
event, # type: Event
|
|
61
62
|
):
|
|
62
63
|
# type: (...) -> None
|
|
63
64
|
self.add_item(Item(payload=PayloadRef(json=event), type="event"))
|
|
64
65
|
|
|
65
66
|
def add_transaction(
|
|
66
|
-
self,
|
|
67
|
+
self,
|
|
68
|
+
transaction, # type: Event
|
|
67
69
|
):
|
|
68
70
|
# type: (...) -> None
|
|
69
71
|
self.add_item(Item(payload=PayloadRef(json=transaction), type="transaction"))
|
|
70
72
|
|
|
71
73
|
def add_profile(
|
|
72
|
-
self,
|
|
74
|
+
self,
|
|
75
|
+
profile, # type: Any
|
|
73
76
|
):
|
|
74
77
|
# type: (...) -> None
|
|
75
78
|
self.add_item(Item(payload=PayloadRef(json=profile), type="profile"))
|
|
76
79
|
|
|
77
80
|
def add_profile_chunk(
|
|
78
|
-
self,
|
|
81
|
+
self,
|
|
82
|
+
profile_chunk, # type: Any
|
|
79
83
|
):
|
|
80
84
|
# type: (...) -> None
|
|
81
85
|
self.add_item(
|
|
@@ -87,13 +91,15 @@ class Envelope:
|
|
|
87
91
|
)
|
|
88
92
|
|
|
89
93
|
def add_checkin(
|
|
90
|
-
self,
|
|
94
|
+
self,
|
|
95
|
+
checkin, # type: Any
|
|
91
96
|
):
|
|
92
97
|
# type: (...) -> None
|
|
93
98
|
self.add_item(Item(payload=PayloadRef(json=checkin), type="check_in"))
|
|
94
99
|
|
|
95
100
|
def add_session(
|
|
96
|
-
self,
|
|
101
|
+
self,
|
|
102
|
+
session, # type: Union[Session, Any]
|
|
97
103
|
):
|
|
98
104
|
# type: (...) -> None
|
|
99
105
|
if isinstance(session, Session):
|
|
@@ -101,13 +107,15 @@ class Envelope:
|
|
|
101
107
|
self.add_item(Item(payload=PayloadRef(json=session), type="session"))
|
|
102
108
|
|
|
103
109
|
def add_sessions(
|
|
104
|
-
self,
|
|
110
|
+
self,
|
|
111
|
+
sessions, # type: Any
|
|
105
112
|
):
|
|
106
113
|
# type: (...) -> None
|
|
107
114
|
self.add_item(Item(payload=PayloadRef(json=sessions), type="sessions"))
|
|
108
115
|
|
|
109
116
|
def add_item(
|
|
110
|
-
self,
|
|
117
|
+
self,
|
|
118
|
+
item, # type: Item
|
|
111
119
|
):
|
|
112
120
|
# type: (...) -> None
|
|
113
121
|
self.items.append(item)
|
|
@@ -133,7 +141,8 @@ class Envelope:
|
|
|
133
141
|
return iter(self.items)
|
|
134
142
|
|
|
135
143
|
def serialize_into(
|
|
136
|
-
self,
|
|
144
|
+
self,
|
|
145
|
+
f, # type: Any
|
|
137
146
|
):
|
|
138
147
|
# type: (...) -> None
|
|
139
148
|
f.write(json_dumps(self.headers))
|
|
@@ -149,7 +158,8 @@ class Envelope:
|
|
|
149
158
|
|
|
150
159
|
@classmethod
|
|
151
160
|
def deserialize_from(
|
|
152
|
-
cls,
|
|
161
|
+
cls,
|
|
162
|
+
f, # type: Any
|
|
153
163
|
):
|
|
154
164
|
# type: (...) -> Envelope
|
|
155
165
|
headers = parse_json(f.readline())
|
|
@@ -163,7 +173,8 @@ class Envelope:
|
|
|
163
173
|
|
|
164
174
|
@classmethod
|
|
165
175
|
def deserialize(
|
|
166
|
-
cls,
|
|
176
|
+
cls,
|
|
177
|
+
bytes, # type: bytes
|
|
167
178
|
):
|
|
168
179
|
# type: (...) -> Envelope
|
|
169
180
|
return cls.deserialize_from(io.BytesIO(bytes))
|
|
@@ -307,7 +318,8 @@ class Item:
|
|
|
307
318
|
return None
|
|
308
319
|
|
|
309
320
|
def serialize_into(
|
|
310
|
-
self,
|
|
321
|
+
self,
|
|
322
|
+
f, # type: Any
|
|
311
323
|
):
|
|
312
324
|
# type: (...) -> None
|
|
313
325
|
headers = dict(self.headers)
|
|
@@ -326,7 +338,8 @@ class Item:
|
|
|
326
338
|
|
|
327
339
|
@classmethod
|
|
328
340
|
def deserialize_from(
|
|
329
|
-
cls,
|
|
341
|
+
cls,
|
|
342
|
+
f, # type: Any
|
|
330
343
|
):
|
|
331
344
|
# type: (...) -> Optional[Item]
|
|
332
345
|
line = f.readline().rstrip()
|
|
@@ -349,7 +362,8 @@ class Item:
|
|
|
349
362
|
|
|
350
363
|
@classmethod
|
|
351
364
|
def deserialize(
|
|
352
|
-
cls,
|
|
365
|
+
cls,
|
|
366
|
+
bytes, # type: bytes
|
|
353
367
|
):
|
|
354
368
|
# type: (...) -> Optional[Item]
|
|
355
369
|
return cls.deserialize_from(io.BytesIO(bytes))
|
sentry_sdk/feature_flags.py
CHANGED
sentry_sdk/hub.py
CHANGED
|
@@ -205,7 +205,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
205
205
|
scope._isolation_scope.set(old_isolation_scope)
|
|
206
206
|
|
|
207
207
|
def run(
|
|
208
|
-
self,
|
|
208
|
+
self,
|
|
209
|
+
callback, # type: Callable[[], T]
|
|
209
210
|
):
|
|
210
211
|
# type: (...) -> T
|
|
211
212
|
"""
|
|
@@ -219,7 +220,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
219
220
|
return callback()
|
|
220
221
|
|
|
221
222
|
def get_integration(
|
|
222
|
-
self,
|
|
223
|
+
self,
|
|
224
|
+
name_or_class, # type: Union[str, Type[Integration]]
|
|
223
225
|
):
|
|
224
226
|
# type: (...) -> Any
|
|
225
227
|
"""
|
|
@@ -277,7 +279,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
277
279
|
return self._last_event_id
|
|
278
280
|
|
|
279
281
|
def bind_client(
|
|
280
|
-
self,
|
|
282
|
+
self,
|
|
283
|
+
new, # type: Optional[BaseClient]
|
|
281
284
|
):
|
|
282
285
|
# type: (...) -> None
|
|
283
286
|
"""
|
|
@@ -430,7 +433,7 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
430
433
|
transaction=None,
|
|
431
434
|
instrumenter=INSTRUMENTER.SENTRY,
|
|
432
435
|
custom_sampling_context=None,
|
|
433
|
-
**kwargs
|
|
436
|
+
**kwargs,
|
|
434
437
|
):
|
|
435
438
|
# type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan]
|
|
436
439
|
"""
|
|
@@ -487,14 +490,16 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
487
490
|
|
|
488
491
|
@overload
|
|
489
492
|
def push_scope(
|
|
490
|
-
self,
|
|
493
|
+
self,
|
|
494
|
+
callback=None, # type: Optional[None]
|
|
491
495
|
):
|
|
492
496
|
# type: (...) -> ContextManager[Scope]
|
|
493
497
|
pass
|
|
494
498
|
|
|
495
499
|
@overload
|
|
496
500
|
def push_scope( # noqa: F811
|
|
497
|
-
self,
|
|
501
|
+
self,
|
|
502
|
+
callback, # type: Callable[[Scope], None]
|
|
498
503
|
):
|
|
499
504
|
# type: (...) -> None
|
|
500
505
|
pass
|
|
@@ -540,14 +545,16 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
540
545
|
|
|
541
546
|
@overload
|
|
542
547
|
def configure_scope(
|
|
543
|
-
self,
|
|
548
|
+
self,
|
|
549
|
+
callback=None, # type: Optional[None]
|
|
544
550
|
):
|
|
545
551
|
# type: (...) -> ContextManager[Scope]
|
|
546
552
|
pass
|
|
547
553
|
|
|
548
554
|
@overload
|
|
549
555
|
def configure_scope( # noqa: F811
|
|
550
|
-
self,
|
|
556
|
+
self,
|
|
557
|
+
callback, # type: Callable[[Scope], None]
|
|
551
558
|
):
|
|
552
559
|
# type: (...) -> None
|
|
553
560
|
pass
|
|
@@ -587,7 +594,8 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
|
|
|
587
594
|
return inner()
|
|
588
595
|
|
|
589
596
|
def start_session(
|
|
590
|
-
self,
|
|
597
|
+
self,
|
|
598
|
+
session_mode="application", # type: str
|
|
591
599
|
):
|
|
592
600
|
# type: (...) -> None
|
|
593
601
|
"""
|
|
@@ -141,10 +141,12 @@ _MIN_VERSIONS = {
|
|
|
141
141
|
"gql": (3, 4, 1),
|
|
142
142
|
"graphene": (3, 3),
|
|
143
143
|
"grpc": (1, 32, 0), # grpcio
|
|
144
|
+
"httpx": (0, 16, 0),
|
|
144
145
|
"huggingface_hub": (0, 24, 7),
|
|
145
146
|
"langchain": (0, 1, 0),
|
|
146
147
|
"langgraph": (0, 6, 6),
|
|
147
148
|
"launchdarkly": (9, 8, 0),
|
|
149
|
+
"litellm": (1, 77, 5),
|
|
148
150
|
"loguru": (0, 7, 0),
|
|
149
151
|
"openai": (1, 0, 0),
|
|
150
152
|
"openai_agents": (0, 0, 19),
|
|
@@ -4,9 +4,10 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
import sentry_sdk
|
|
5
5
|
from sentry_sdk.ai.monitoring import record_token_usage
|
|
6
6
|
from sentry_sdk.ai.utils import set_data_normalized, get_start_span_function
|
|
7
|
-
from sentry_sdk.consts import OP, SPANDATA
|
|
7
|
+
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
|
|
8
8
|
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
|
|
9
9
|
from sentry_sdk.scope import should_send_default_pii
|
|
10
|
+
from sentry_sdk.tracing_utils import set_span_errored
|
|
10
11
|
from sentry_sdk.utils import (
|
|
11
12
|
capture_internal_exceptions,
|
|
12
13
|
event_from_exception,
|
|
@@ -52,6 +53,8 @@ class AnthropicIntegration(Integration):
|
|
|
52
53
|
|
|
53
54
|
def _capture_exception(exc):
|
|
54
55
|
# type: (Any) -> None
|
|
56
|
+
set_span_errored()
|
|
57
|
+
|
|
55
58
|
event, hint = event_from_exception(
|
|
56
59
|
exc,
|
|
57
60
|
client_options=sentry_sdk.get_client().options,
|
|
@@ -357,7 +360,13 @@ def _wrap_message_create(f):
|
|
|
357
360
|
integration = sentry_sdk.get_client().get_integration(AnthropicIntegration)
|
|
358
361
|
kwargs["integration"] = integration
|
|
359
362
|
|
|
360
|
-
|
|
363
|
+
try:
|
|
364
|
+
return _execute_sync(f, *args, **kwargs)
|
|
365
|
+
finally:
|
|
366
|
+
span = sentry_sdk.get_current_span()
|
|
367
|
+
if span is not None and span.status == SPANSTATUS.ERROR:
|
|
368
|
+
with capture_internal_exceptions():
|
|
369
|
+
span.__exit__(None, None, None)
|
|
361
370
|
|
|
362
371
|
return _sentry_patched_create_sync
|
|
363
372
|
|
|
@@ -390,6 +399,12 @@ def _wrap_message_create_async(f):
|
|
|
390
399
|
integration = sentry_sdk.get_client().get_integration(AnthropicIntegration)
|
|
391
400
|
kwargs["integration"] = integration
|
|
392
401
|
|
|
393
|
-
|
|
402
|
+
try:
|
|
403
|
+
return await _execute_async(f, *args, **kwargs)
|
|
404
|
+
finally:
|
|
405
|
+
span = sentry_sdk.get_current_span()
|
|
406
|
+
if span is not None and span.status == SPANSTATUS.ERROR:
|
|
407
|
+
with capture_internal_exceptions():
|
|
408
|
+
span.__exit__(None, None, None)
|
|
394
409
|
|
|
395
410
|
return _sentry_patched_create_async
|
sentry_sdk/integrations/asgi.py
CHANGED
|
@@ -233,14 +233,15 @@ class SentryAsgiMiddleware:
|
|
|
233
233
|
if transaction:
|
|
234
234
|
transaction.set_tag("asgi.type", ty)
|
|
235
235
|
|
|
236
|
-
|
|
236
|
+
transaction_context = (
|
|
237
237
|
sentry_sdk.start_transaction(
|
|
238
238
|
transaction,
|
|
239
239
|
custom_sampling_context={"asgi_scope": scope},
|
|
240
240
|
)
|
|
241
241
|
if transaction is not None
|
|
242
242
|
else nullcontext()
|
|
243
|
-
)
|
|
243
|
+
)
|
|
244
|
+
with transaction_context:
|
|
244
245
|
try:
|
|
245
246
|
|
|
246
247
|
async def _sentry_wrapped_send(event):
|
|
@@ -7,6 +7,8 @@ from sentry_sdk.ai.utils import set_data_normalized
|
|
|
7
7
|
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
|
+
from sentry_sdk.tracing_utils import set_span_errored
|
|
11
|
+
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from typing import Any, Callable, Iterator
|
|
12
14
|
from sentry_sdk.tracing import Span
|
|
@@ -84,6 +86,8 @@ class CohereIntegration(Integration):
|
|
|
84
86
|
|
|
85
87
|
def _capture_exception(exc):
|
|
86
88
|
# type: (Any) -> None
|
|
89
|
+
set_span_errored()
|
|
90
|
+
|
|
87
91
|
event, hint = event_from_exception(
|
|
88
92
|
exc,
|
|
89
93
|
client_options=sentry_sdk.get_client().options,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
|
|
1
3
|
import sentry_sdk
|
|
2
4
|
from sentry_sdk.utils import ContextVar, logger
|
|
3
5
|
from sentry_sdk.integrations import Integration
|
|
@@ -35,12 +37,24 @@ class DedupeIntegration(Integration):
|
|
|
35
37
|
if exc_info is None:
|
|
36
38
|
return event
|
|
37
39
|
|
|
40
|
+
last_seen = integration._last_seen.get(None)
|
|
41
|
+
if last_seen is not None:
|
|
42
|
+
# last_seen is either a weakref or the original instance
|
|
43
|
+
last_seen = (
|
|
44
|
+
last_seen() if isinstance(last_seen, weakref.ref) else last_seen
|
|
45
|
+
)
|
|
46
|
+
|
|
38
47
|
exc = exc_info[1]
|
|
39
|
-
if
|
|
48
|
+
if last_seen is exc:
|
|
40
49
|
logger.info("DedupeIntegration dropped duplicated error event %s", exc)
|
|
41
50
|
return None
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
# we can only weakref non builtin types
|
|
53
|
+
try:
|
|
54
|
+
integration._last_seen.set(weakref.ref(exc))
|
|
55
|
+
except TypeError:
|
|
56
|
+
integration._last_seen.set(exc)
|
|
57
|
+
|
|
44
58
|
return event
|
|
45
59
|
|
|
46
60
|
@staticmethod
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
3
|
import sentry_sdk
|
|
4
|
-
from sentry_sdk.
|
|
4
|
+
from sentry_sdk.consts import OP, SPANSTATUS
|
|
5
|
+
from sentry_sdk.api import continue_trace, get_baggage, get_traceparent
|
|
6
|
+
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
5
7
|
from sentry_sdk.integrations._wsgi_common import request_body_within_bounds
|
|
8
|
+
from sentry_sdk.tracing import (
|
|
9
|
+
BAGGAGE_HEADER_NAME,
|
|
10
|
+
SENTRY_TRACE_HEADER_NAME,
|
|
11
|
+
TransactionSource,
|
|
12
|
+
)
|
|
6
13
|
from sentry_sdk.utils import (
|
|
7
14
|
AnnotatedValue,
|
|
8
15
|
capture_internal_exceptions,
|
|
9
16
|
event_from_exception,
|
|
10
17
|
)
|
|
18
|
+
from typing import TypeVar
|
|
19
|
+
|
|
20
|
+
R = TypeVar("R")
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
from dramatiq.
|
|
14
|
-
from dramatiq.middleware import Middleware, default_middleware
|
|
15
|
-
from dramatiq.errors import Retry
|
|
22
|
+
try:
|
|
23
|
+
from dramatiq.broker import Broker
|
|
24
|
+
from dramatiq.middleware import Middleware, default_middleware
|
|
25
|
+
from dramatiq.errors import Retry
|
|
26
|
+
from dramatiq.message import Message
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise DidNotEnable("Dramatiq is not installed")
|
|
16
29
|
|
|
17
30
|
from typing import TYPE_CHECKING
|
|
18
31
|
|
|
@@ -34,10 +47,12 @@ class DramatiqIntegration(Integration):
|
|
|
34
47
|
"""
|
|
35
48
|
|
|
36
49
|
identifier = "dramatiq"
|
|
50
|
+
origin = f"auto.queue.{identifier}"
|
|
37
51
|
|
|
38
52
|
@staticmethod
|
|
39
53
|
def setup_once():
|
|
40
54
|
# type: () -> None
|
|
55
|
+
|
|
41
56
|
_patch_dramatiq_broker()
|
|
42
57
|
|
|
43
58
|
|
|
@@ -85,22 +100,54 @@ class SentryMiddleware(Middleware): # type: ignore[misc]
|
|
|
85
100
|
DramatiqIntegration.
|
|
86
101
|
"""
|
|
87
102
|
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
SENTRY_HEADERS_NAME = "_sentry_headers"
|
|
104
|
+
|
|
105
|
+
def before_enqueue(self, broker, message, delay):
|
|
106
|
+
# type: (Broker, Message[R], int) -> None
|
|
90
107
|
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
91
108
|
if integration is None:
|
|
92
109
|
return
|
|
93
110
|
|
|
94
|
-
message.
|
|
95
|
-
|
|
111
|
+
message.options[self.SENTRY_HEADERS_NAME] = {
|
|
112
|
+
BAGGAGE_HEADER_NAME: get_baggage(),
|
|
113
|
+
SENTRY_TRACE_HEADER_NAME: get_traceparent(),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def before_process_message(self, broker, message):
|
|
117
|
+
# type: (Broker, Message[R]) -> None
|
|
118
|
+
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
119
|
+
if integration is None:
|
|
120
|
+
return
|
|
96
121
|
|
|
97
|
-
|
|
98
|
-
scope.
|
|
122
|
+
message._scope_manager = sentry_sdk.isolation_scope()
|
|
123
|
+
scope = message._scope_manager.__enter__()
|
|
124
|
+
scope.clear_breadcrumbs()
|
|
99
125
|
scope.set_extra("dramatiq_message_id", message.message_id)
|
|
100
126
|
scope.add_event_processor(_make_message_event_processor(message, integration))
|
|
101
127
|
|
|
128
|
+
sentry_headers = message.options.get(self.SENTRY_HEADERS_NAME) or {}
|
|
129
|
+
if "retries" in message.options:
|
|
130
|
+
# start new trace in case of retrying
|
|
131
|
+
sentry_headers = {}
|
|
132
|
+
|
|
133
|
+
transaction = continue_trace(
|
|
134
|
+
sentry_headers,
|
|
135
|
+
name=message.actor_name,
|
|
136
|
+
op=OP.QUEUE_TASK_DRAMATIQ,
|
|
137
|
+
source=TransactionSource.TASK,
|
|
138
|
+
origin=DramatiqIntegration.origin,
|
|
139
|
+
)
|
|
140
|
+
transaction.set_status(SPANSTATUS.OK)
|
|
141
|
+
sentry_sdk.start_transaction(
|
|
142
|
+
transaction,
|
|
143
|
+
name=message.actor_name,
|
|
144
|
+
op=OP.QUEUE_TASK_DRAMATIQ,
|
|
145
|
+
source=TransactionSource.TASK,
|
|
146
|
+
)
|
|
147
|
+
transaction.__enter__()
|
|
148
|
+
|
|
102
149
|
def after_process_message(self, broker, message, *, result=None, exception=None):
|
|
103
|
-
# type: (Broker, Message,
|
|
150
|
+
# type: (Broker, Message[R], Optional[Any], Optional[Exception]) -> None
|
|
104
151
|
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
105
152
|
if integration is None:
|
|
106
153
|
return
|
|
@@ -108,27 +155,38 @@ class SentryMiddleware(Middleware): # type: ignore[misc]
|
|
|
108
155
|
actor = broker.get_actor(message.actor_name)
|
|
109
156
|
throws = message.options.get("throws") or actor.options.get("throws")
|
|
110
157
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
scope_manager = message._scope_manager
|
|
159
|
+
transaction = sentry_sdk.get_current_scope().transaction
|
|
160
|
+
if not transaction:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
is_event_capture_required = (
|
|
164
|
+
exception is not None
|
|
165
|
+
and not (throws and isinstance(exception, throws))
|
|
166
|
+
and not isinstance(exception, Retry)
|
|
167
|
+
)
|
|
168
|
+
if not is_event_capture_required:
|
|
169
|
+
# normal transaction finish
|
|
170
|
+
transaction.__exit__(None, None, None)
|
|
171
|
+
scope_manager.__exit__(None, None, None)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
event, hint = event_from_exception(
|
|
175
|
+
exception, # type: ignore[arg-type]
|
|
176
|
+
client_options=sentry_sdk.get_client().options,
|
|
177
|
+
mechanism={
|
|
178
|
+
"type": DramatiqIntegration.identifier,
|
|
179
|
+
"handled": False,
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
183
|
+
# transaction error
|
|
184
|
+
transaction.__exit__(type(exception), exception, None)
|
|
185
|
+
scope_manager.__exit__(type(exception), exception, None)
|
|
128
186
|
|
|
129
187
|
|
|
130
188
|
def _make_message_event_processor(message, integration):
|
|
131
|
-
# type: (Message, DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]]
|
|
189
|
+
# type: (Message[R], DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]]
|
|
132
190
|
|
|
133
191
|
def inner(event, hint):
|
|
134
192
|
# type: (Event, Hint) -> Optional[Event]
|
|
@@ -142,7 +200,7 @@ def _make_message_event_processor(message, integration):
|
|
|
142
200
|
|
|
143
201
|
class DramatiqMessageExtractor:
|
|
144
202
|
def __init__(self, message):
|
|
145
|
-
# type: (Message) -> None
|
|
203
|
+
# type: (Message[R]) -> None
|
|
146
204
|
self.message_data = dict(message.asdict())
|
|
147
205
|
|
|
148
206
|
def content_length(self):
|
|
@@ -65,7 +65,8 @@ class SentryUnaryUnaryClientInterceptor(ClientInterceptor, UnaryUnaryClientInter
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
class SentryUnaryStreamClientInterceptor(
|
|
68
|
-
ClientInterceptor,
|
|
68
|
+
ClientInterceptor,
|
|
69
|
+
UnaryStreamClientInterceptor, # type: ignore
|
|
69
70
|
):
|
|
70
71
|
async def intercept_unary_stream(
|
|
71
72
|
self,
|
|
@@ -19,7 +19,8 @@ except ImportError:
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class ClientInterceptor(
|
|
22
|
-
grpc.UnaryUnaryClientInterceptor,
|
|
22
|
+
grpc.UnaryUnaryClientInterceptor, # type: ignore
|
|
23
|
+
grpc.UnaryStreamClientInterceptor, # type: ignore
|
|
23
24
|
):
|
|
24
25
|
_is_intercepted = False
|
|
25
26
|
|
|
@@ -60,9 +61,7 @@ class ClientInterceptor(
|
|
|
60
61
|
client_call_details
|
|
61
62
|
)
|
|
62
63
|
|
|
63
|
-
response = continuation(
|
|
64
|
-
client_call_details, request
|
|
65
|
-
) # type: UnaryStreamCall
|
|
64
|
+
response = continuation(client_call_details, request) # type: UnaryStreamCall
|
|
66
65
|
# Setting code on unary-stream leads to execution getting stuck
|
|
67
66
|
# span.set_data("code", response.code().name)
|
|
68
67
|
|