sentry-sdk 2.34.1__py2.py3-none-any.whl → 2.35.1__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 (33) hide show
  1. sentry_sdk/__init__.py +1 -0
  2. sentry_sdk/ai/utils.py +9 -9
  3. sentry_sdk/api.py +80 -0
  4. sentry_sdk/client.py +12 -7
  5. sentry_sdk/consts.py +135 -10
  6. sentry_sdk/crons/api.py +5 -0
  7. sentry_sdk/integrations/anthropic.py +138 -75
  8. sentry_sdk/integrations/asgi.py +11 -25
  9. sentry_sdk/integrations/clickhouse_driver.py +33 -13
  10. sentry_sdk/integrations/django/asgi.py +1 -1
  11. sentry_sdk/integrations/fastapi.py +1 -7
  12. sentry_sdk/integrations/gnu_backtrace.py +6 -3
  13. sentry_sdk/integrations/langchain.py +466 -193
  14. sentry_sdk/integrations/litestar.py +1 -1
  15. sentry_sdk/integrations/logging.py +2 -1
  16. sentry_sdk/integrations/loguru.py +2 -1
  17. sentry_sdk/integrations/openai.py +8 -2
  18. sentry_sdk/integrations/openai_agents/patches/agent_run.py +0 -3
  19. sentry_sdk/integrations/openai_agents/patches/runner.py +18 -15
  20. sentry_sdk/integrations/openfeature.py +2 -4
  21. sentry_sdk/integrations/quart.py +1 -1
  22. sentry_sdk/integrations/starlette.py +1 -5
  23. sentry_sdk/integrations/starlite.py +1 -1
  24. sentry_sdk/scope.py +11 -11
  25. sentry_sdk/tracing.py +118 -25
  26. sentry_sdk/tracing_utils.py +321 -39
  27. sentry_sdk/utils.py +22 -1
  28. {sentry_sdk-2.34.1.dist-info → sentry_sdk-2.35.1.dist-info}/METADATA +1 -1
  29. {sentry_sdk-2.34.1.dist-info → sentry_sdk-2.35.1.dist-info}/RECORD +33 -33
  30. {sentry_sdk-2.34.1.dist-info → sentry_sdk-2.35.1.dist-info}/WHEEL +0 -0
  31. {sentry_sdk-2.34.1.dist-info → sentry_sdk-2.35.1.dist-info}/entry_points.txt +0 -0
  32. {sentry_sdk-2.34.1.dist-info → sentry_sdk-2.35.1.dist-info}/licenses/LICENSE +0 -0
  33. {sentry_sdk-2.34.1.dist-info → sentry_sdk-2.35.1.dist-info}/top_level.txt +0 -0
@@ -85,6 +85,7 @@ class SentryLitestarASGIMiddleware(SentryAsgiMiddleware):
85
85
  transaction_style="endpoint",
86
86
  mechanism_type="asgi",
87
87
  span_origin=span_origin,
88
+ asgi_version=3,
88
89
  )
89
90
 
90
91
  def _capture_request_exception(self, exc):
@@ -116,7 +117,6 @@ def patch_app_init():
116
117
  *(kwargs.get("after_exception") or []),
117
118
  ]
118
119
 
119
- SentryLitestarASGIMiddleware.__call__ = SentryLitestarASGIMiddleware._run_asgi3 # type: ignore
120
120
  middleware = kwargs.get("middleware") or []
121
121
  kwargs["middleware"] = [SentryLitestarASGIMiddleware, *middleware]
122
122
  old__init__(self, *args, **kwargs)
@@ -12,6 +12,7 @@ from sentry_sdk.utils import (
12
12
  event_from_exception,
13
13
  current_stacktrace,
14
14
  capture_internal_exceptions,
15
+ has_logs_enabled,
15
16
  )
16
17
  from sentry_sdk.integrations import Integration
17
18
 
@@ -344,7 +345,7 @@ class SentryLogsHandler(_BaseHandler):
344
345
  if not client.is_active():
345
346
  return
346
347
 
347
- if not client.options["_experiments"].get("enable_logs", False):
348
+ if not has_logs_enabled(client.options):
348
349
  return
349
350
 
350
351
  self._capture_log_from_record(client, record)
@@ -8,6 +8,7 @@ from sentry_sdk.integrations.logging import (
8
8
  _BaseHandler,
9
9
  )
10
10
  from sentry_sdk.logger import _log_level_to_otel
11
+ from sentry_sdk.utils import has_logs_enabled
11
12
 
12
13
  from typing import TYPE_CHECKING
13
14
 
@@ -151,7 +152,7 @@ def loguru_sentry_logs_handler(message):
151
152
  if not client.is_active():
152
153
  return
153
154
 
154
- if not client.options["_experiments"].get("enable_logs", False):
155
+ if not has_logs_enabled(client.options):
155
156
  return
156
157
 
157
158
  record = message.record
@@ -20,6 +20,11 @@ if TYPE_CHECKING:
20
20
  from sentry_sdk.tracing import Span
21
21
 
22
22
  try:
23
+ try:
24
+ from openai import NOT_GIVEN
25
+ except ImportError:
26
+ NOT_GIVEN = None
27
+
23
28
  from openai.resources.chat.completions import Completions, AsyncCompletions
24
29
  from openai.resources import Embeddings, AsyncEmbeddings
25
30
 
@@ -192,12 +197,13 @@ def _set_input_data(span, kwargs, operation, integration):
192
197
  }
193
198
  for key, attribute in kwargs_keys_to_attributes.items():
194
199
  value = kwargs.get(key)
195
- if value is not None:
200
+
201
+ if value is not NOT_GIVEN and value is not None:
196
202
  set_data_normalized(span, attribute, value)
197
203
 
198
204
  # Input attributes: Tools
199
205
  tools = kwargs.get("tools")
200
- if tools is not None and len(tools) > 0:
206
+ if tools is not NOT_GIVEN and tools is not None and len(tools) > 0:
201
207
  set_data_normalized(
202
208
  span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools)
203
209
  )
@@ -1,7 +1,6 @@
1
1
  from functools import wraps
2
2
 
3
3
  from sentry_sdk.integrations import DidNotEnable
4
-
5
4
  from ..spans import invoke_agent_span, update_invoke_agent_span, handoff_span
6
5
 
7
6
  from typing import TYPE_CHECKING
@@ -9,7 +8,6 @@ from typing import TYPE_CHECKING
9
8
  if TYPE_CHECKING:
10
9
  from typing import Any, Optional
11
10
 
12
-
13
11
  try:
14
12
  import agents
15
13
  except ImportError:
@@ -62,7 +60,6 @@ def _patch_agent_run():
62
60
  async def patched_run_single_turn(cls, *args, **kwargs):
63
61
  # type: (agents.Runner, *Any, **Any) -> Any
64
62
  """Patched _run_single_turn that creates agent invocation spans"""
65
-
66
63
  agent = kwargs.get("agent")
67
64
  context_wrapper = kwargs.get("context_wrapper")
68
65
  should_run_agent_start_hooks = kwargs.get("should_run_agent_start_hooks")
@@ -23,20 +23,23 @@ def _create_run_wrapper(original_func):
23
23
  @wraps(original_func)
24
24
  async def wrapper(*args, **kwargs):
25
25
  # type: (*Any, **Any) -> Any
26
- agent = args[0]
27
- with agent_workflow_span(agent):
28
- result = None
29
- try:
30
- result = await original_func(*args, **kwargs)
31
- return result
32
- except Exception as exc:
33
- _capture_exception(exc)
34
-
35
- # It could be that there is a "invoke agent" span still open
36
- current_span = sentry_sdk.get_current_span()
37
- if current_span is not None and current_span.timestamp is None:
38
- current_span.__exit__(None, None, None)
39
-
40
- raise exc from None
26
+ # Isolate each workflow so that when agents are run in asyncio tasks they
27
+ # don't touch each other's scopes
28
+ with sentry_sdk.isolation_scope():
29
+ agent = args[0]
30
+ with agent_workflow_span(agent):
31
+ result = None
32
+ try:
33
+ result = await original_func(*args, **kwargs)
34
+ return result
35
+ except Exception as exc:
36
+ _capture_exception(exc)
37
+
38
+ # It could be that there is a "invoke agent" span still open
39
+ current_span = sentry_sdk.get_current_span()
40
+ if current_span is not None and current_span.timestamp is None:
41
+ current_span.__exit__(None, None, None)
42
+
43
+ raise exc from None
41
44
 
42
45
  return wrapper
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  from sentry_sdk.feature_flags import add_feature_flag
4
4
  from sentry_sdk.integrations import DidNotEnable, Integration
@@ -8,7 +8,6 @@ try:
8
8
  from openfeature.hook import Hook
9
9
 
10
10
  if TYPE_CHECKING:
11
- from openfeature.flag_evaluation import FlagEvaluationDetails
12
11
  from openfeature.hook import HookContext, HookHints
13
12
  except ImportError:
14
13
  raise DidNotEnable("OpenFeature is not installed")
@@ -25,9 +24,8 @@ class OpenFeatureIntegration(Integration):
25
24
 
26
25
 
27
26
  class OpenFeatureHook(Hook):
28
-
29
27
  def after(self, hook_context, details, hints):
30
- # type: (HookContext, FlagEvaluationDetails[bool], HookHints) -> None
28
+ # type: (Any, Any, Any) -> None
31
29
  if isinstance(details.value, bool):
32
30
  add_feature_flag(details.flag_key, details.value)
33
31
 
@@ -95,8 +95,8 @@ def patch_asgi_app():
95
95
  middleware = SentryAsgiMiddleware(
96
96
  lambda *a, **kw: old_app(self, *a, **kw),
97
97
  span_origin=QuartIntegration.origin,
98
+ asgi_version=3,
98
99
  )
99
- middleware.__call__ = middleware._run_asgi3
100
100
  return await middleware(scope, receive, send)
101
101
 
102
102
  Quart.__call__ = sentry_patched_asgi_app
@@ -29,7 +29,6 @@ from sentry_sdk.utils import (
29
29
  capture_internal_exceptions,
30
30
  ensure_integration_enabled,
31
31
  event_from_exception,
32
- logger,
33
32
  parse_version,
34
33
  transaction_from_function,
35
34
  )
@@ -403,9 +402,9 @@ def patch_asgi_app():
403
402
  if integration
404
403
  else DEFAULT_HTTP_METHODS_TO_CAPTURE
405
404
  ),
405
+ asgi_version=3,
406
406
  )
407
407
 
408
- middleware.__call__ = middleware._run_asgi3
409
408
  return await middleware(scope, receive, send)
410
409
 
411
410
  Starlette.__call__ = _sentry_patched_asgi_app
@@ -723,9 +722,6 @@ def _set_transaction_name_and_source(scope, transaction_style, request):
723
722
  source = TransactionSource.ROUTE
724
723
 
725
724
  scope.set_transaction_name(name, source=source)
726
- logger.debug(
727
- "[Starlette] Set transaction name and source on scope: %s / %s", name, source
728
- )
729
725
 
730
726
 
731
727
  def _get_transaction_from_middleware(app, asgi_scope, integration):
@@ -65,6 +65,7 @@ class SentryStarliteASGIMiddleware(SentryAsgiMiddleware):
65
65
  transaction_style="endpoint",
66
66
  mechanism_type="asgi",
67
67
  span_origin=span_origin,
68
+ asgi_version=3,
68
69
  )
69
70
 
70
71
 
@@ -94,7 +95,6 @@ def patch_app_init():
94
95
  ]
95
96
  )
96
97
 
97
- SentryStarliteASGIMiddleware.__call__ = SentryStarliteASGIMiddleware._run_asgi3 # type: ignore
98
98
  middleware = kwargs.get("middleware") or []
99
99
  kwargs["middleware"] = [SentryStarliteASGIMiddleware, *middleware]
100
100
  old__init__(self, *args, **kwargs)
sentry_sdk/scope.py CHANGED
@@ -48,7 +48,7 @@ import typing
48
48
  from typing import TYPE_CHECKING
49
49
 
50
50
  if TYPE_CHECKING:
51
- from collections.abc import Mapping, MutableMapping
51
+ from collections.abc import Mapping
52
52
 
53
53
  from typing import Any
54
54
  from typing import Callable
@@ -238,24 +238,24 @@ class Scope:
238
238
  rv._name = self._name
239
239
  rv._fingerprint = self._fingerprint
240
240
  rv._transaction = self._transaction
241
- rv._transaction_info = dict(self._transaction_info)
241
+ rv._transaction_info = self._transaction_info.copy()
242
242
  rv._user = self._user
243
243
 
244
- rv._tags = dict(self._tags)
245
- rv._contexts = dict(self._contexts)
246
- rv._extras = dict(self._extras)
244
+ rv._tags = self._tags.copy()
245
+ rv._contexts = self._contexts.copy()
246
+ rv._extras = self._extras.copy()
247
247
 
248
248
  rv._breadcrumbs = copy(self._breadcrumbs)
249
- rv._n_breadcrumbs_truncated = copy(self._n_breadcrumbs_truncated)
250
- rv._event_processors = list(self._event_processors)
251
- rv._error_processors = list(self._error_processors)
249
+ rv._n_breadcrumbs_truncated = self._n_breadcrumbs_truncated
250
+ rv._event_processors = self._event_processors.copy()
251
+ rv._error_processors = self._error_processors.copy()
252
252
  rv._propagation_context = self._propagation_context
253
253
 
254
254
  rv._should_capture = self._should_capture
255
255
  rv._span = self._span
256
256
  rv._session = self._session
257
257
  rv._force_auto_session_tracking = self._force_auto_session_tracking
258
- rv._attachments = list(self._attachments)
258
+ rv._attachments = self._attachments.copy()
259
259
 
260
260
  rv._profile = self._profile
261
261
 
@@ -683,12 +683,12 @@ class Scope:
683
683
  self._level = None # type: Optional[LogLevelStr]
684
684
  self._fingerprint = None # type: Optional[List[str]]
685
685
  self._transaction = None # type: Optional[str]
686
- self._transaction_info = {} # type: MutableMapping[str, str]
686
+ self._transaction_info = {} # type: dict[str, str]
687
687
  self._user = None # type: Optional[Dict[str, Any]]
688
688
 
689
689
  self._tags = {} # type: Dict[str, Any]
690
690
  self._contexts = {} # type: Dict[str, Dict[str, Any]]
691
- self._extras = {} # type: MutableMapping[str, Any]
691
+ self._extras = {} # type: dict[str, Any]
692
692
  self._attachments = [] # type: List[Attachment]
693
693
 
694
694
  self.clear_breadcrumbs()
sentry_sdk/tracing.py CHANGED
@@ -5,7 +5,7 @@ from datetime import datetime, timedelta, timezone
5
5
  from enum import Enum
6
6
 
7
7
  import sentry_sdk
8
- from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA
8
+ from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA, SPANTEMPLATE
9
9
  from sentry_sdk.profiler.continuous_profiler import get_profiler_id
10
10
  from sentry_sdk.utils import (
11
11
  get_current_thread_meta,
@@ -257,8 +257,8 @@ class Span:
257
257
  """
258
258
 
259
259
  __slots__ = (
260
- "trace_id",
261
- "span_id",
260
+ "_trace_id",
261
+ "_span_id",
262
262
  "parent_span_id",
263
263
  "same_process_as_parent",
264
264
  "sampled",
@@ -301,8 +301,8 @@ class Span:
301
301
  name=None, # type: Optional[str]
302
302
  ):
303
303
  # type: (...) -> None
304
- self.trace_id = trace_id or uuid.uuid4().hex
305
- self.span_id = span_id or uuid.uuid4().hex[16:]
304
+ self._trace_id = trace_id
305
+ self._span_id = span_id
306
306
  self.parent_span_id = parent_span_id
307
307
  self.same_process_as_parent = same_process_as_parent
308
308
  self.sampled = sampled
@@ -356,6 +356,32 @@ class Span:
356
356
  if self._span_recorder is None:
357
357
  self._span_recorder = _SpanRecorder(maxlen)
358
358
 
359
+ @property
360
+ def trace_id(self):
361
+ # type: () -> str
362
+ if not self._trace_id:
363
+ self._trace_id = uuid.uuid4().hex
364
+
365
+ return self._trace_id
366
+
367
+ @trace_id.setter
368
+ def trace_id(self, value):
369
+ # type: (str) -> None
370
+ self._trace_id = value
371
+
372
+ @property
373
+ def span_id(self):
374
+ # type: () -> str
375
+ if not self._span_id:
376
+ self._span_id = uuid.uuid4().hex[16:]
377
+
378
+ return self._span_id
379
+
380
+ @span_id.setter
381
+ def span_id(self, value):
382
+ # type: (str) -> None
383
+ self._span_id = value
384
+
359
385
  def _get_local_aggregator(self):
360
386
  # type: (...) -> LocalAggregator
361
387
  rv = self._local_aggregator
@@ -602,6 +628,10 @@ class Span:
602
628
  # type: (str, Any) -> None
603
629
  self._data[key] = value
604
630
 
631
+ def update_data(self, data):
632
+ # type: (Dict[str, Any]) -> None
633
+ self._data.update(data)
634
+
605
635
  def set_flag(self, flag, result):
606
636
  # type: (str, bool) -> None
607
637
  if len(self._flags) < self._flags_capacity:
@@ -818,7 +848,6 @@ class Transaction(Span):
818
848
  **kwargs, # type: Unpack[SpanKwargs]
819
849
  ):
820
850
  # type: (...) -> None
821
-
822
851
  super().__init__(**kwargs)
823
852
 
824
853
  self.name = name
@@ -1275,6 +1304,10 @@ class NoOpSpan(Span):
1275
1304
  # type: (str, Any) -> None
1276
1305
  pass
1277
1306
 
1307
+ def update_data(self, data):
1308
+ # type: (Dict[str, Any]) -> None
1309
+ pass
1310
+
1278
1311
  def set_status(self, value):
1279
1312
  # type: (str) -> None
1280
1313
  pass
@@ -1332,43 +1365,103 @@ class NoOpSpan(Span):
1332
1365
  if TYPE_CHECKING:
1333
1366
 
1334
1367
  @overload
1335
- def trace(func=None):
1336
- # type: (None) -> Callable[[Callable[P, R]], Callable[P, R]]
1368
+ def trace(
1369
+ func=None, *, op=None, name=None, attributes=None, template=SPANTEMPLATE.DEFAULT
1370
+ ):
1371
+ # type: (None, Optional[str], Optional[str], Optional[dict[str, Any]], SPANTEMPLATE) -> Callable[[Callable[P, R]], Callable[P, R]]
1372
+ # Handles: @trace() and @trace(op="custom")
1337
1373
  pass
1338
1374
 
1339
1375
  @overload
1340
1376
  def trace(func):
1341
1377
  # type: (Callable[P, R]) -> Callable[P, R]
1378
+ # Handles: @trace
1342
1379
  pass
1343
1380
 
1344
1381
 
1345
- def trace(func=None):
1346
- # type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]
1382
+ def trace(
1383
+ func=None, *, op=None, name=None, attributes=None, template=SPANTEMPLATE.DEFAULT
1384
+ ):
1385
+ # type: (Optional[Callable[P, R]], Optional[str], Optional[str], Optional[dict[str, Any]], SPANTEMPLATE) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]
1347
1386
  """
1348
- Decorator to start a child span under the existing current transaction.
1349
- If there is no current transaction, then nothing will be traced.
1350
-
1351
- .. code-block::
1352
- :caption: Usage
1387
+ Decorator to start a child span around a function call.
1388
+
1389
+ This decorator automatically creates a new span when the decorated function
1390
+ is called, and finishes the span when the function returns or raises an exception.
1391
+
1392
+ :param func: The function to trace. When used as a decorator without parentheses,
1393
+ this is the function being decorated. When used with parameters (e.g.,
1394
+ ``@trace(op="custom")``, this should be None.
1395
+ :type func: Callable or None
1396
+
1397
+ :param op: The operation name for the span. This is a high-level description
1398
+ of what the span represents (e.g., "http.client", "db.query").
1399
+ You can use predefined constants from :py:class:`sentry_sdk.consts.OP`
1400
+ or provide your own string. If not provided, a default operation will
1401
+ be assigned based on the template.
1402
+ :type op: str or None
1403
+
1404
+ :param name: The human-readable name/description for the span. If not provided,
1405
+ defaults to the function name. This provides more specific details about
1406
+ what the span represents (e.g., "GET /api/users", "process_user_data").
1407
+ :type name: str or None
1408
+
1409
+ :param attributes: A dictionary of key-value pairs to add as attributes to the span.
1410
+ Attribute values must be strings, integers, floats, or booleans. These
1411
+ attributes provide additional context about the span's execution.
1412
+ :type attributes: dict[str, Any] or None
1413
+
1414
+ :param template: The type of span to create. This determines what kind of
1415
+ span instrumentation and data collection will be applied. Use predefined
1416
+ constants from :py:class:`sentry_sdk.consts.SPANTEMPLATE`.
1417
+ The default is `SPANTEMPLATE.DEFAULT` which is the right choice for most
1418
+ use cases.
1419
+ :type template: :py:class:`sentry_sdk.consts.SPANTEMPLATE`
1420
+
1421
+ :returns: When used as ``@trace``, returns the decorated function. When used as
1422
+ ``@trace(...)`` with parameters, returns a decorator function.
1423
+ :rtype: Callable or decorator function
1424
+
1425
+ Example::
1353
1426
 
1354
1427
  import sentry_sdk
1428
+ from sentry_sdk.consts import OP, SPANTEMPLATE
1355
1429
 
1430
+ # Simple usage with default values
1356
1431
  @sentry_sdk.trace
1357
- def my_function():
1358
- ...
1432
+ def process_data():
1433
+ # Function implementation
1434
+ pass
1359
1435
 
1360
- @sentry_sdk.trace
1361
- async def my_async_function():
1362
- ...
1436
+ # With custom parameters
1437
+ @sentry_sdk.trace(
1438
+ op=OP.DB_QUERY,
1439
+ name="Get user data",
1440
+ attributes={"postgres": True}
1441
+ )
1442
+ def make_db_query(sql):
1443
+ # Function implementation
1444
+ pass
1445
+
1446
+ # With a custom template
1447
+ @sentry_sdk.trace(template=SPANTEMPLATE.AI_TOOL)
1448
+ def calculate_interest_rate(amount, rate, years):
1449
+ # Function implementation
1450
+ pass
1363
1451
  """
1364
- from sentry_sdk.tracing_utils import start_child_span_decorator
1452
+ from sentry_sdk.tracing_utils import create_span_decorator
1453
+
1454
+ decorator = create_span_decorator(
1455
+ op=op,
1456
+ name=name,
1457
+ attributes=attributes,
1458
+ template=template,
1459
+ )
1365
1460
 
1366
- # This patterns allows usage of both @sentry_traced and @sentry_traced(...)
1367
- # See https://stackoverflow.com/questions/52126071/decorator-with-arguments-avoid-parenthesis-when-no-arguments/52126278
1368
1461
  if func:
1369
- return start_child_span_decorator(func)
1462
+ return decorator(func)
1370
1463
  else:
1371
- return start_child_span_decorator
1464
+ return decorator
1372
1465
 
1373
1466
 
1374
1467
  # Circular imports