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.

Files changed (44) hide show
  1. sentry_sdk/client.py +6 -6
  2. sentry_sdk/consts.py +15 -3
  3. sentry_sdk/envelope.py +28 -14
  4. sentry_sdk/feature_flags.py +0 -1
  5. sentry_sdk/hub.py +17 -9
  6. sentry_sdk/integrations/__init__.py +2 -0
  7. sentry_sdk/integrations/anthropic.py +18 -3
  8. sentry_sdk/integrations/asgi.py +3 -2
  9. sentry_sdk/integrations/cohere.py +4 -0
  10. sentry_sdk/integrations/dedupe.py +16 -2
  11. sentry_sdk/integrations/dramatiq.py +89 -31
  12. sentry_sdk/integrations/grpc/aio/client.py +2 -1
  13. sentry_sdk/integrations/grpc/client.py +3 -4
  14. sentry_sdk/integrations/huggingface_hub.py +3 -2
  15. sentry_sdk/integrations/langchain.py +12 -12
  16. sentry_sdk/integrations/launchdarkly.py +0 -1
  17. sentry_sdk/integrations/litellm.py +251 -0
  18. sentry_sdk/integrations/litestar.py +4 -4
  19. sentry_sdk/integrations/openai.py +13 -8
  20. sentry_sdk/integrations/openai_agents/spans/ai_client.py +4 -1
  21. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +1 -1
  22. sentry_sdk/integrations/openai_agents/utils.py +28 -1
  23. sentry_sdk/integrations/pure_eval.py +3 -1
  24. sentry_sdk/integrations/spark/spark_driver.py +2 -1
  25. sentry_sdk/integrations/sqlalchemy.py +2 -6
  26. sentry_sdk/integrations/starlette.py +1 -3
  27. sentry_sdk/integrations/starlite.py +4 -4
  28. sentry_sdk/integrations/wsgi.py +3 -2
  29. sentry_sdk/metrics.py +17 -11
  30. sentry_sdk/profiler/utils.py +2 -6
  31. sentry_sdk/scope.py +6 -3
  32. sentry_sdk/serializer.py +13 -4
  33. sentry_sdk/session.py +4 -2
  34. sentry_sdk/sessions.py +4 -2
  35. sentry_sdk/tracing.py +38 -8
  36. sentry_sdk/tracing_utils.py +15 -4
  37. sentry_sdk/transport.py +8 -9
  38. sentry_sdk/utils.py +5 -3
  39. {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/METADATA +3 -1
  40. {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/RECORD +44 -43
  41. {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/WHEEL +0 -0
  42. {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/entry_points.txt +0 -0
  43. {sentry_sdk-2.38.0.dist-info → sentry_sdk-2.40.0.dist-info}/licenses/LICENSE +0 -0
  44. {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'[Sentry Logs] [{log.get("severity_text")}] {log.get("body")}'
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, session # type: Session
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, name_or_class # type: Union[str, Type[Integration]]
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.38.0"
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, event # type: Event
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, transaction # type: Event
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, profile # type: Any
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, profile_chunk # type: Any
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, checkin # type: Any
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, session # type: Union[Session, Any]
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, sessions # type: Any
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, item # type: Item
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, f # type: Any
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, f # type: Any
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, bytes # type: bytes
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, f # type: Any
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, f # type: Any
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, bytes # type: bytes
365
+ cls,
366
+ bytes, # type: bytes
353
367
  ):
354
368
  # type: (...) -> Optional[Item]
355
369
  return cls.deserialize_from(io.BytesIO(bytes))
@@ -15,7 +15,6 @@ DEFAULT_FLAG_CAPACITY = 100
15
15
 
16
16
 
17
17
  class FlagBuffer:
18
-
19
18
  def __init__(self, capacity):
20
19
  # type: (int) -> None
21
20
  self.capacity = capacity
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, callback # type: Callable[[], T]
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, name_or_class # type: Union[str, Type[Integration]]
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, new # type: Optional[BaseClient]
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, callback=None # type: Optional[None]
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, callback # type: Callable[[Scope], None]
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, callback=None # type: Optional[None]
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, callback # type: Callable[[Scope], None]
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, session_mode="application" # type: str
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
- return _execute_sync(f, *args, **kwargs)
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
- return await _execute_async(f, *args, **kwargs)
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
@@ -233,14 +233,15 @@ class SentryAsgiMiddleware:
233
233
  if transaction:
234
234
  transaction.set_tag("asgi.type", ty)
235
235
 
236
- with (
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 integration._last_seen.get(None) is exc:
48
+ if last_seen is exc:
40
49
  logger.info("DedupeIntegration dropped duplicated error event %s", exc)
41
50
  return None
42
51
 
43
- integration._last_seen.set(exc)
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.integrations import Integration
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
- from dramatiq.broker import Broker # type: ignore
13
- from dramatiq.message import Message # type: ignore
14
- from dramatiq.middleware import Middleware, default_middleware # type: ignore
15
- from dramatiq.errors import Retry # type: ignore
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
- def before_process_message(self, broker, message):
89
- # type: (Broker, Message) -> None
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._scope_manager = sentry_sdk.new_scope()
95
- message._scope_manager.__enter__()
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
- scope = sentry_sdk.get_current_scope()
98
- scope.set_transaction_name(message.actor_name)
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, Any, Optional[Any], Optional[Exception]) -> None
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
- try:
112
- if (
113
- exception is not None
114
- and not (throws and isinstance(exception, throws))
115
- and not isinstance(exception, Retry)
116
- ):
117
- event, hint = event_from_exception(
118
- exception,
119
- client_options=sentry_sdk.get_client().options,
120
- mechanism={
121
- "type": DramatiqIntegration.identifier,
122
- "handled": False,
123
- },
124
- )
125
- sentry_sdk.capture_event(event, hint=hint)
126
- finally:
127
- message._scope_manager.__exit__(None, None, None)
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, UnaryStreamClientInterceptor # type: ignore
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, grpc.UnaryStreamClientInterceptor # type: ignore
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