sentry-sdk 2.30.0__py2.py3-none-any.whl → 3.0.0a2__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/__init__.py +3 -8
- sentry_sdk/_compat.py +0 -1
- sentry_sdk/_init_implementation.py +6 -44
- sentry_sdk/_types.py +2 -64
- sentry_sdk/ai/monitoring.py +14 -10
- sentry_sdk/ai/utils.py +1 -1
- sentry_sdk/api.py +56 -169
- sentry_sdk/client.py +27 -72
- sentry_sdk/consts.py +60 -23
- sentry_sdk/debug.py +0 -10
- sentry_sdk/envelope.py +1 -3
- sentry_sdk/feature_flags.py +1 -1
- sentry_sdk/integrations/__init__.py +4 -2
- sentry_sdk/integrations/_asgi_common.py +5 -6
- sentry_sdk/integrations/_wsgi_common.py +11 -40
- sentry_sdk/integrations/aiohttp.py +104 -57
- sentry_sdk/integrations/anthropic.py +10 -7
- sentry_sdk/integrations/arq.py +24 -13
- sentry_sdk/integrations/asgi.py +102 -83
- sentry_sdk/integrations/asyncio.py +1 -0
- sentry_sdk/integrations/asyncpg.py +45 -30
- sentry_sdk/integrations/aws_lambda.py +109 -92
- sentry_sdk/integrations/boto3.py +38 -9
- sentry_sdk/integrations/bottle.py +1 -1
- sentry_sdk/integrations/celery/__init__.py +51 -41
- sentry_sdk/integrations/clickhouse_driver.py +59 -28
- sentry_sdk/integrations/cohere.py +2 -0
- sentry_sdk/integrations/django/__init__.py +25 -46
- sentry_sdk/integrations/django/asgi.py +6 -2
- sentry_sdk/integrations/django/caching.py +13 -22
- sentry_sdk/integrations/django/middleware.py +1 -0
- sentry_sdk/integrations/django/signals_handlers.py +3 -1
- sentry_sdk/integrations/django/templates.py +8 -12
- sentry_sdk/integrations/django/transactions.py +1 -6
- sentry_sdk/integrations/django/views.py +5 -2
- sentry_sdk/integrations/falcon.py +7 -25
- sentry_sdk/integrations/fastapi.py +3 -3
- sentry_sdk/integrations/flask.py +1 -1
- sentry_sdk/integrations/gcp.py +63 -38
- sentry_sdk/integrations/graphene.py +6 -13
- sentry_sdk/integrations/grpc/aio/client.py +14 -8
- sentry_sdk/integrations/grpc/aio/server.py +19 -21
- sentry_sdk/integrations/grpc/client.py +8 -6
- sentry_sdk/integrations/grpc/server.py +12 -14
- sentry_sdk/integrations/httpx.py +47 -12
- sentry_sdk/integrations/huey.py +26 -22
- sentry_sdk/integrations/huggingface_hub.py +1 -0
- sentry_sdk/integrations/langchain.py +22 -15
- sentry_sdk/integrations/litestar.py +4 -2
- sentry_sdk/integrations/logging.py +7 -2
- sentry_sdk/integrations/openai.py +2 -0
- sentry_sdk/integrations/pymongo.py +18 -25
- sentry_sdk/integrations/pyramid.py +1 -1
- sentry_sdk/integrations/quart.py +3 -3
- sentry_sdk/integrations/ray.py +23 -17
- sentry_sdk/integrations/redis/_async_common.py +29 -18
- sentry_sdk/integrations/redis/_sync_common.py +28 -19
- sentry_sdk/integrations/redis/modules/caches.py +13 -10
- sentry_sdk/integrations/redis/modules/queries.py +14 -11
- sentry_sdk/integrations/redis/rb.py +4 -4
- sentry_sdk/integrations/redis/redis.py +6 -6
- sentry_sdk/integrations/redis/redis_cluster.py +18 -18
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
- sentry_sdk/integrations/redis/utils.py +64 -24
- sentry_sdk/integrations/rq.py +68 -23
- sentry_sdk/integrations/rust_tracing.py +28 -43
- sentry_sdk/integrations/sanic.py +23 -13
- sentry_sdk/integrations/socket.py +9 -5
- sentry_sdk/integrations/sqlalchemy.py +8 -8
- sentry_sdk/integrations/starlette.py +11 -31
- sentry_sdk/integrations/starlite.py +4 -2
- sentry_sdk/integrations/stdlib.py +56 -9
- sentry_sdk/integrations/strawberry.py +40 -59
- sentry_sdk/integrations/threading.py +10 -26
- sentry_sdk/integrations/tornado.py +57 -18
- sentry_sdk/integrations/trytond.py +4 -1
- sentry_sdk/integrations/wsgi.py +84 -38
- sentry_sdk/opentelemetry/__init__.py +9 -0
- sentry_sdk/opentelemetry/consts.py +33 -0
- sentry_sdk/opentelemetry/contextvars_context.py +81 -0
- sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
- sentry_sdk/opentelemetry/sampler.py +326 -0
- sentry_sdk/opentelemetry/scope.py +218 -0
- sentry_sdk/opentelemetry/span_processor.py +335 -0
- sentry_sdk/opentelemetry/tracing.py +59 -0
- sentry_sdk/opentelemetry/utils.py +484 -0
- sentry_sdk/profiler/__init__.py +0 -40
- sentry_sdk/profiler/continuous_profiler.py +1 -30
- sentry_sdk/profiler/transaction_profiler.py +5 -56
- sentry_sdk/scope.py +108 -361
- sentry_sdk/sessions.py +0 -87
- sentry_sdk/tracing.py +415 -1161
- sentry_sdk/tracing_utils.py +130 -166
- sentry_sdk/transport.py +4 -104
- sentry_sdk/utils.py +169 -152
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/METADATA +3 -5
- sentry_sdk-3.0.0a2.dist-info/RECORD +154 -0
- sentry_sdk-3.0.0a2.dist-info/entry_points.txt +2 -0
- sentry_sdk/hub.py +0 -739
- sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
- sentry_sdk/integrations/opentelemetry/consts.py +0 -5
- sentry_sdk/integrations/opentelemetry/integration.py +0 -58
- sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
- sentry_sdk/metrics.py +0 -965
- sentry_sdk-2.30.0.dist-info/RECORD +0 -152
- sentry_sdk-2.30.0.dist-info/entry_points.txt +0 -2
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/WHEEL +0 -0
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/top_level.txt +0 -0
sentry_sdk/transport.py
CHANGED
|
@@ -5,7 +5,6 @@ import gzip
|
|
|
5
5
|
import socket
|
|
6
6
|
import ssl
|
|
7
7
|
import time
|
|
8
|
-
import warnings
|
|
9
8
|
from datetime import datetime, timedelta, timezone
|
|
10
9
|
from collections import defaultdict
|
|
11
10
|
from urllib.request import getproxies
|
|
@@ -18,7 +17,6 @@ except ImportError:
|
|
|
18
17
|
import urllib3
|
|
19
18
|
import certifi
|
|
20
19
|
|
|
21
|
-
import sentry_sdk
|
|
22
20
|
from sentry_sdk.consts import EndpointType
|
|
23
21
|
from sentry_sdk.utils import Dsn, logger, capture_internal_exceptions
|
|
24
22
|
from sentry_sdk.worker import BackgroundWorker
|
|
@@ -41,7 +39,7 @@ if TYPE_CHECKING:
|
|
|
41
39
|
from urllib3.poolmanager import PoolManager
|
|
42
40
|
from urllib3.poolmanager import ProxyManager
|
|
43
41
|
|
|
44
|
-
from sentry_sdk._types import
|
|
42
|
+
from sentry_sdk._types import EventDataCategory
|
|
45
43
|
|
|
46
44
|
KEEP_ALIVE_SOCKET_OPTIONS = []
|
|
47
45
|
for option in [
|
|
@@ -74,25 +72,6 @@ class Transport(ABC):
|
|
|
74
72
|
else:
|
|
75
73
|
self.parsed_dsn = None
|
|
76
74
|
|
|
77
|
-
def capture_event(self, event):
|
|
78
|
-
# type: (Self, Event) -> None
|
|
79
|
-
"""
|
|
80
|
-
DEPRECATED: Please use capture_envelope instead.
|
|
81
|
-
|
|
82
|
-
This gets invoked with the event dictionary when an event should
|
|
83
|
-
be sent to sentry.
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
warnings.warn(
|
|
87
|
-
"capture_event is deprecated, please use capture_envelope instead!",
|
|
88
|
-
DeprecationWarning,
|
|
89
|
-
stacklevel=2,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
envelope = Envelope()
|
|
93
|
-
envelope.add_event(event)
|
|
94
|
-
self.capture_envelope(envelope)
|
|
95
|
-
|
|
96
75
|
@abstractmethod
|
|
97
76
|
def capture_envelope(self, envelope):
|
|
98
77
|
# type: (Self, Envelope) -> None
|
|
@@ -178,17 +157,8 @@ def _parse_rate_limits(header, now=None):
|
|
|
178
157
|
|
|
179
158
|
retry_after = now + timedelta(seconds=int(retry_after_val))
|
|
180
159
|
for category in categories and categories.split(";") or (None,):
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
namespaces = parameters[4].split(";")
|
|
184
|
-
except IndexError:
|
|
185
|
-
namespaces = []
|
|
186
|
-
|
|
187
|
-
if not namespaces or "custom" in namespaces:
|
|
188
|
-
yield category, retry_after # type: ignore
|
|
189
|
-
|
|
190
|
-
else:
|
|
191
|
-
yield category, retry_after # type: ignore
|
|
160
|
+
category = cast("Optional[EventDataCategory]", category)
|
|
161
|
+
yield category, retry_after
|
|
192
162
|
except (LookupError, ValueError):
|
|
193
163
|
continue
|
|
194
164
|
|
|
@@ -217,9 +187,6 @@ class BaseHttpTransport(Transport):
|
|
|
217
187
|
|
|
218
188
|
self._pool = self._make_pool()
|
|
219
189
|
|
|
220
|
-
# Backwards compatibility for deprecated `self.hub_class` attribute
|
|
221
|
-
self._hub_cls = sentry_sdk.Hub
|
|
222
|
-
|
|
223
190
|
experiments = options.get("_experiments", {})
|
|
224
191
|
compression_level = experiments.get(
|
|
225
192
|
"transport_compression_level",
|
|
@@ -426,12 +393,6 @@ class BaseHttpTransport(Transport):
|
|
|
426
393
|
# type: (str) -> bool
|
|
427
394
|
def _disabled(bucket):
|
|
428
395
|
# type: (Any) -> bool
|
|
429
|
-
|
|
430
|
-
# The envelope item type used for metrics is statsd
|
|
431
|
-
# whereas the rate limit category is metric_bucket
|
|
432
|
-
if bucket == "statsd":
|
|
433
|
-
bucket = "metric_bucket"
|
|
434
|
-
|
|
435
396
|
ts = self._disabled_until.get(bucket)
|
|
436
397
|
return ts is not None and ts > datetime.now(timezone.utc)
|
|
437
398
|
|
|
@@ -458,7 +419,7 @@ class BaseHttpTransport(Transport):
|
|
|
458
419
|
new_items = []
|
|
459
420
|
for item in envelope.items:
|
|
460
421
|
if self._check_disabled(item.data_category):
|
|
461
|
-
if item.data_category in ("transaction", "error", "default"
|
|
422
|
+
if item.data_category in ("transaction", "error", "default"):
|
|
462
423
|
self.on_dropped_event("self_rate_limits")
|
|
463
424
|
self.record_lost_event("ratelimit_backoff", item=item)
|
|
464
425
|
else:
|
|
@@ -587,30 +548,6 @@ class BaseHttpTransport(Transport):
|
|
|
587
548
|
logger.debug("Killing HTTP transport")
|
|
588
549
|
self._worker.kill()
|
|
589
550
|
|
|
590
|
-
@staticmethod
|
|
591
|
-
def _warn_hub_cls():
|
|
592
|
-
# type: () -> None
|
|
593
|
-
"""Convenience method to warn users about the deprecation of the `hub_cls` attribute."""
|
|
594
|
-
warnings.warn(
|
|
595
|
-
"The `hub_cls` attribute is deprecated and will be removed in a future release.",
|
|
596
|
-
DeprecationWarning,
|
|
597
|
-
stacklevel=3,
|
|
598
|
-
)
|
|
599
|
-
|
|
600
|
-
@property
|
|
601
|
-
def hub_cls(self):
|
|
602
|
-
# type: (Self) -> type[sentry_sdk.Hub]
|
|
603
|
-
"""DEPRECATED: This attribute is deprecated and will be removed in a future release."""
|
|
604
|
-
HttpTransport._warn_hub_cls()
|
|
605
|
-
return self._hub_cls
|
|
606
|
-
|
|
607
|
-
@hub_cls.setter
|
|
608
|
-
def hub_cls(self, value):
|
|
609
|
-
# type: (Self, type[sentry_sdk.Hub]) -> None
|
|
610
|
-
"""DEPRECATED: This attribute is deprecated and will be removed in a future release."""
|
|
611
|
-
HttpTransport._warn_hub_cls()
|
|
612
|
-
self._hub_cls = value
|
|
613
|
-
|
|
614
551
|
|
|
615
552
|
class HttpTransport(BaseHttpTransport):
|
|
616
553
|
if TYPE_CHECKING:
|
|
@@ -862,35 +799,6 @@ else:
|
|
|
862
799
|
return httpcore.ConnectionPool(**opts)
|
|
863
800
|
|
|
864
801
|
|
|
865
|
-
class _FunctionTransport(Transport):
|
|
866
|
-
"""
|
|
867
|
-
DEPRECATED: Users wishing to provide a custom transport should subclass
|
|
868
|
-
the Transport class, rather than providing a function.
|
|
869
|
-
"""
|
|
870
|
-
|
|
871
|
-
def __init__(
|
|
872
|
-
self, func # type: Callable[[Event], None]
|
|
873
|
-
):
|
|
874
|
-
# type: (...) -> None
|
|
875
|
-
Transport.__init__(self)
|
|
876
|
-
self._func = func
|
|
877
|
-
|
|
878
|
-
def capture_event(
|
|
879
|
-
self, event # type: Event
|
|
880
|
-
):
|
|
881
|
-
# type: (...) -> None
|
|
882
|
-
self._func(event)
|
|
883
|
-
return None
|
|
884
|
-
|
|
885
|
-
def capture_envelope(self, envelope: Envelope) -> None:
|
|
886
|
-
# Since function transports expect to be called with an event, we need
|
|
887
|
-
# to iterate over the envelope and call the function for each event, via
|
|
888
|
-
# the deprecated capture_event method.
|
|
889
|
-
event = envelope.get_event()
|
|
890
|
-
if event is not None:
|
|
891
|
-
self.capture_event(event)
|
|
892
|
-
|
|
893
|
-
|
|
894
802
|
def make_transport(options):
|
|
895
803
|
# type: (Dict[str, Any]) -> Optional[Transport]
|
|
896
804
|
ref_transport = options["transport"]
|
|
@@ -906,14 +814,6 @@ def make_transport(options):
|
|
|
906
814
|
return ref_transport
|
|
907
815
|
elif isinstance(ref_transport, type) and issubclass(ref_transport, Transport):
|
|
908
816
|
transport_cls = ref_transport
|
|
909
|
-
elif callable(ref_transport):
|
|
910
|
-
warnings.warn(
|
|
911
|
-
"Function transports are deprecated and will be removed in a future release."
|
|
912
|
-
"Please provide a Transport instance or subclass, instead.",
|
|
913
|
-
DeprecationWarning,
|
|
914
|
-
stacklevel=2,
|
|
915
|
-
)
|
|
916
|
-
return _FunctionTransport(ref_transport)
|
|
917
817
|
|
|
918
818
|
# if a transport class is given only instantiate it if the dsn is not
|
|
919
819
|
# empty or None
|
sentry_sdk/utils.py
CHANGED
|
@@ -25,11 +25,11 @@ except ImportError:
|
|
|
25
25
|
BaseExceptionGroup = None # type: ignore
|
|
26
26
|
|
|
27
27
|
import sentry_sdk
|
|
28
|
-
from sentry_sdk._compat import PY37
|
|
29
28
|
from sentry_sdk.consts import (
|
|
30
29
|
DEFAULT_ADD_FULL_STACK,
|
|
31
30
|
DEFAULT_MAX_STACK_FRAMES,
|
|
32
31
|
DEFAULT_MAX_VALUE_LENGTH,
|
|
32
|
+
SPANDATA,
|
|
33
33
|
EndpointType,
|
|
34
34
|
)
|
|
35
35
|
from sentry_sdk._types import Annotated, AnnotatedValue, SENSITIVE_DATA_SUBSTITUTE
|
|
@@ -57,7 +57,8 @@ if TYPE_CHECKING:
|
|
|
57
57
|
Union,
|
|
58
58
|
)
|
|
59
59
|
|
|
60
|
-
from gevent.hub import Hub
|
|
60
|
+
from gevent.hub import Hub as GeventHub
|
|
61
|
+
from opentelemetry.util.types import AttributeValue
|
|
61
62
|
|
|
62
63
|
from sentry_sdk._types import Event, ExcInfo
|
|
63
64
|
|
|
@@ -86,6 +87,13 @@ exceeds the default sys.getrecursionlimit() of 1000, so users will only
|
|
|
86
87
|
be affected by this limit if they have a custom recursion limit.
|
|
87
88
|
"""
|
|
88
89
|
|
|
90
|
+
MAX_EXCEPTIONS = 25
|
|
91
|
+
"""Maximum number of exceptions in a chain or group to send to Sentry.
|
|
92
|
+
|
|
93
|
+
This is a sanity limit to avoid ending in an infinite loop of exceptions when the same exception is in the root and a leave
|
|
94
|
+
of the exception tree.
|
|
95
|
+
"""
|
|
96
|
+
|
|
89
97
|
|
|
90
98
|
def env_to_bool(value, *, strict=False):
|
|
91
99
|
# type: (Any, Optional[bool]) -> bool | None
|
|
@@ -250,31 +258,6 @@ def format_timestamp(value):
|
|
|
250
258
|
return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
251
259
|
|
|
252
260
|
|
|
253
|
-
ISO_TZ_SEPARATORS = frozenset(("+", "-"))
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def datetime_from_isoformat(value):
|
|
257
|
-
# type: (str) -> datetime
|
|
258
|
-
try:
|
|
259
|
-
result = datetime.fromisoformat(value)
|
|
260
|
-
except (AttributeError, ValueError):
|
|
261
|
-
# py 3.6
|
|
262
|
-
timestamp_format = (
|
|
263
|
-
"%Y-%m-%dT%H:%M:%S.%f" if "." in value else "%Y-%m-%dT%H:%M:%S"
|
|
264
|
-
)
|
|
265
|
-
if value.endswith("Z"):
|
|
266
|
-
value = value[:-1] + "+0000"
|
|
267
|
-
|
|
268
|
-
if value[-6] in ISO_TZ_SEPARATORS:
|
|
269
|
-
timestamp_format += "%z"
|
|
270
|
-
value = value[:-3] + value[-2:]
|
|
271
|
-
elif value[-5] in ISO_TZ_SEPARATORS:
|
|
272
|
-
timestamp_format += "%z"
|
|
273
|
-
|
|
274
|
-
result = datetime.strptime(value, timestamp_format)
|
|
275
|
-
return result.astimezone(timezone.utc)
|
|
276
|
-
|
|
277
|
-
|
|
278
261
|
def event_hint_with_exc_info(exc_info=None):
|
|
279
262
|
# type: (Optional[ExcInfo]) -> Dict[str, Optional[ExcInfo]]
|
|
280
263
|
"""Creates a hint with the exc info filled in."""
|
|
@@ -822,14 +805,17 @@ def exceptions_from_error(
|
|
|
822
805
|
):
|
|
823
806
|
# type: (...) -> Tuple[int, List[Dict[str, Any]]]
|
|
824
807
|
"""
|
|
825
|
-
|
|
826
|
-
This
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
808
|
+
Converts the given exception information into the Sentry structured "exception" format.
|
|
809
|
+
This will return a list of exceptions (a flattened tree of exceptions) in the
|
|
810
|
+
format of the Exception Interface documentation:
|
|
811
|
+
https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
|
|
812
|
+
|
|
813
|
+
This function can handle:
|
|
814
|
+
- simple exceptions
|
|
815
|
+
- chained exceptions (raise .. from ..)
|
|
816
|
+
- exception groups
|
|
830
817
|
"""
|
|
831
|
-
|
|
832
|
-
parent = single_exception_from_error_tuple(
|
|
818
|
+
base_exception = single_exception_from_error_tuple(
|
|
833
819
|
exc_type=exc_type,
|
|
834
820
|
exc_value=exc_value,
|
|
835
821
|
tb=tb,
|
|
@@ -840,64 +826,79 @@ def exceptions_from_error(
|
|
|
840
826
|
source=source,
|
|
841
827
|
full_stack=full_stack,
|
|
842
828
|
)
|
|
843
|
-
exceptions = [
|
|
829
|
+
exceptions = [base_exception]
|
|
844
830
|
|
|
845
831
|
parent_id = exception_id
|
|
846
832
|
exception_id += 1
|
|
847
833
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
834
|
+
if exception_id > MAX_EXCEPTIONS - 1:
|
|
835
|
+
return (exception_id, exceptions)
|
|
836
|
+
|
|
837
|
+
causing_exception = None
|
|
838
|
+
exception_source = None
|
|
839
|
+
|
|
840
|
+
# Add any causing exceptions, if present.
|
|
841
|
+
should_suppress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore
|
|
842
|
+
# Note: __suppress_context__ is True if the exception is raised with the `from` keyword.
|
|
843
|
+
if should_suppress_context:
|
|
844
|
+
# Explicitly chained exceptions (Like: raise NewException() from OriginalException())
|
|
845
|
+
# The field `__cause__` is set to OriginalException
|
|
846
|
+
has_explicit_causing_exception = (
|
|
853
847
|
exc_value
|
|
854
848
|
and hasattr(exc_value, "__cause__")
|
|
855
849
|
and exc_value.__cause__ is not None
|
|
856
850
|
)
|
|
857
|
-
if
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
exc_type=type(cause),
|
|
861
|
-
exc_value=cause,
|
|
862
|
-
tb=getattr(cause, "__traceback__", None),
|
|
863
|
-
client_options=client_options,
|
|
864
|
-
mechanism=mechanism,
|
|
865
|
-
exception_id=exception_id,
|
|
866
|
-
source="__cause__",
|
|
867
|
-
full_stack=full_stack,
|
|
868
|
-
)
|
|
869
|
-
exceptions.extend(child_exceptions)
|
|
870
|
-
|
|
851
|
+
if has_explicit_causing_exception:
|
|
852
|
+
exception_source = "__cause__"
|
|
853
|
+
causing_exception = exc_value.__cause__ # type: ignore
|
|
871
854
|
else:
|
|
872
|
-
#
|
|
873
|
-
# The field `__context__` is
|
|
874
|
-
|
|
855
|
+
# Implicitly chained exceptions (when an exception occurs while handling another exception)
|
|
856
|
+
# The field `__context__` is set in the exception that occurs while handling another exception,
|
|
857
|
+
# to the other exception.
|
|
858
|
+
has_implicit_causing_exception = (
|
|
875
859
|
exc_value
|
|
876
860
|
and hasattr(exc_value, "__context__")
|
|
877
861
|
and exc_value.__context__ is not None
|
|
878
862
|
)
|
|
879
|
-
if
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
)
|
|
891
|
-
exceptions
|
|
863
|
+
if has_implicit_causing_exception:
|
|
864
|
+
exception_source = "__context__"
|
|
865
|
+
causing_exception = exc_value.__context__ # type: ignore
|
|
866
|
+
|
|
867
|
+
if causing_exception:
|
|
868
|
+
# Some frameworks (e.g. FastAPI) wrap the causing exception in an
|
|
869
|
+
# ExceptionGroup that only contain one exception: the causing exception.
|
|
870
|
+
# This would lead to an infinite loop, so we skip the causing exception
|
|
871
|
+
# in this case. (because it is the same as the base_exception above)
|
|
872
|
+
if (
|
|
873
|
+
BaseExceptionGroup is not None
|
|
874
|
+
and isinstance(causing_exception, BaseExceptionGroup)
|
|
875
|
+
and len(causing_exception.exceptions) == 1
|
|
876
|
+
and causing_exception.exceptions[0] == exc_value
|
|
877
|
+
):
|
|
878
|
+
causing_exception = None
|
|
879
|
+
|
|
880
|
+
if causing_exception:
|
|
881
|
+
(exception_id, child_exceptions) = exceptions_from_error(
|
|
882
|
+
exc_type=type(causing_exception),
|
|
883
|
+
exc_value=causing_exception,
|
|
884
|
+
tb=getattr(causing_exception, "__traceback__", None),
|
|
885
|
+
client_options=client_options,
|
|
886
|
+
mechanism=mechanism,
|
|
887
|
+
exception_id=exception_id,
|
|
888
|
+
parent_id=parent_id,
|
|
889
|
+
source=exception_source,
|
|
890
|
+
full_stack=full_stack,
|
|
891
|
+
)
|
|
892
|
+
exceptions.extend(child_exceptions)
|
|
892
893
|
|
|
893
|
-
# Add exceptions from an ExceptionGroup.
|
|
894
|
+
# Add child exceptions from an ExceptionGroup.
|
|
894
895
|
is_exception_group = exc_value and hasattr(exc_value, "exceptions")
|
|
895
896
|
if is_exception_group:
|
|
896
|
-
for idx,
|
|
897
|
+
for idx, causing_exception in enumerate(exc_value.exceptions): # type: ignore
|
|
897
898
|
(exception_id, child_exceptions) = exceptions_from_error(
|
|
898
|
-
exc_type=type(
|
|
899
|
-
exc_value=
|
|
900
|
-
tb=getattr(
|
|
899
|
+
exc_type=type(causing_exception),
|
|
900
|
+
exc_value=causing_exception,
|
|
901
|
+
tb=getattr(causing_exception, "__traceback__", None),
|
|
901
902
|
client_options=client_options,
|
|
902
903
|
mechanism=mechanism,
|
|
903
904
|
exception_id=exception_id,
|
|
@@ -917,38 +918,29 @@ def exceptions_from_error_tuple(
|
|
|
917
918
|
full_stack=None, # type: Optional[list[dict[str, Any]]]
|
|
918
919
|
):
|
|
919
920
|
# type: (...) -> List[Dict[str, Any]]
|
|
921
|
+
"""
|
|
922
|
+
Convert Python's exception information into Sentry's structured "exception" format in the event.
|
|
923
|
+
See https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
|
|
924
|
+
This is the entry point for the exception handling.
|
|
925
|
+
"""
|
|
926
|
+
# unpack the exception info tuple
|
|
920
927
|
exc_type, exc_value, tb = exc_info
|
|
921
928
|
|
|
922
|
-
|
|
923
|
-
|
|
929
|
+
# let exceptions_from_error do the actual work
|
|
930
|
+
_, exceptions = exceptions_from_error(
|
|
931
|
+
exc_type=exc_type,
|
|
932
|
+
exc_value=exc_value,
|
|
933
|
+
tb=tb,
|
|
934
|
+
client_options=client_options,
|
|
935
|
+
mechanism=mechanism,
|
|
936
|
+
exception_id=0,
|
|
937
|
+
parent_id=0,
|
|
938
|
+
full_stack=full_stack,
|
|
924
939
|
)
|
|
925
940
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
exc_value=exc_value,
|
|
930
|
-
tb=tb,
|
|
931
|
-
client_options=client_options,
|
|
932
|
-
mechanism=mechanism,
|
|
933
|
-
exception_id=0,
|
|
934
|
-
parent_id=0,
|
|
935
|
-
full_stack=full_stack,
|
|
936
|
-
)
|
|
937
|
-
|
|
938
|
-
else:
|
|
939
|
-
exceptions = []
|
|
940
|
-
for exc_type, exc_value, tb in walk_exception_chain(exc_info):
|
|
941
|
-
exceptions.append(
|
|
942
|
-
single_exception_from_error_tuple(
|
|
943
|
-
exc_type=exc_type,
|
|
944
|
-
exc_value=exc_value,
|
|
945
|
-
tb=tb,
|
|
946
|
-
client_options=client_options,
|
|
947
|
-
mechanism=mechanism,
|
|
948
|
-
full_stack=full_stack,
|
|
949
|
-
)
|
|
950
|
-
)
|
|
951
|
-
|
|
941
|
+
# make sure the exceptions are sorted
|
|
942
|
+
# from the innermost (oldest)
|
|
943
|
+
# to the outermost (newest) exception
|
|
952
944
|
exceptions.reverse()
|
|
953
945
|
|
|
954
946
|
return exceptions
|
|
@@ -1372,27 +1364,13 @@ def _get_contextvars():
|
|
|
1372
1364
|
See https://docs.sentry.io/platforms/python/contextvars/ for more information.
|
|
1373
1365
|
"""
|
|
1374
1366
|
if not _is_contextvars_broken():
|
|
1375
|
-
#
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
# Import it if available.
|
|
1379
|
-
if sys.version_info < (3, 7):
|
|
1380
|
-
# `aiocontextvars` is absolutely required for functional
|
|
1381
|
-
# contextvars on Python 3.6.
|
|
1382
|
-
try:
|
|
1383
|
-
from aiocontextvars import ContextVar
|
|
1384
|
-
|
|
1385
|
-
return True, ContextVar
|
|
1386
|
-
except ImportError:
|
|
1387
|
-
pass
|
|
1388
|
-
else:
|
|
1389
|
-
# On Python 3.7 contextvars are functional.
|
|
1390
|
-
try:
|
|
1391
|
-
from contextvars import ContextVar
|
|
1367
|
+
# On Python 3.7+ contextvars are functional.
|
|
1368
|
+
try:
|
|
1369
|
+
from contextvars import ContextVar
|
|
1392
1370
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1371
|
+
return True, ContextVar
|
|
1372
|
+
except ImportError:
|
|
1373
|
+
pass
|
|
1396
1374
|
|
|
1397
1375
|
# Fall back to basic thread-local usage.
|
|
1398
1376
|
|
|
@@ -1792,7 +1770,7 @@ def ensure_integration_enabled(
|
|
|
1792
1770
|
```python
|
|
1793
1771
|
@ensure_integration_enabled(MyIntegration, my_function)
|
|
1794
1772
|
def patch_my_function():
|
|
1795
|
-
with sentry_sdk.
|
|
1773
|
+
with sentry_sdk.start_span(...):
|
|
1796
1774
|
return my_function()
|
|
1797
1775
|
```
|
|
1798
1776
|
"""
|
|
@@ -1818,19 +1796,6 @@ def ensure_integration_enabled(
|
|
|
1818
1796
|
return patcher
|
|
1819
1797
|
|
|
1820
1798
|
|
|
1821
|
-
if PY37:
|
|
1822
|
-
|
|
1823
|
-
def nanosecond_time():
|
|
1824
|
-
# type: () -> int
|
|
1825
|
-
return time.perf_counter_ns()
|
|
1826
|
-
|
|
1827
|
-
else:
|
|
1828
|
-
|
|
1829
|
-
def nanosecond_time():
|
|
1830
|
-
# type: () -> int
|
|
1831
|
-
return int(time.perf_counter() * 1e9)
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
1799
|
def now():
|
|
1835
1800
|
# type: () -> float
|
|
1836
1801
|
return time.perf_counter()
|
|
@@ -1842,9 +1807,9 @@ try:
|
|
|
1842
1807
|
except ImportError:
|
|
1843
1808
|
|
|
1844
1809
|
# it's not great that the signatures are different, get_hub can't return None
|
|
1845
|
-
# consider adding an if TYPE_CHECKING to change the signature to Optional[
|
|
1810
|
+
# consider adding an if TYPE_CHECKING to change the signature to Optional[GeventHub]
|
|
1846
1811
|
def get_gevent_hub(): # type: ignore[misc]
|
|
1847
|
-
# type: () -> Optional[
|
|
1812
|
+
# type: () -> Optional[GeventHub]
|
|
1848
1813
|
return None
|
|
1849
1814
|
|
|
1850
1815
|
def is_module_patched(mod_name):
|
|
@@ -1909,6 +1874,56 @@ def get_current_thread_meta(thread=None):
|
|
|
1909
1874
|
return None, None
|
|
1910
1875
|
|
|
1911
1876
|
|
|
1877
|
+
def _serialize_span_attribute(value):
|
|
1878
|
+
# type: (Any) -> Optional[AttributeValue]
|
|
1879
|
+
"""Serialize an object so that it's OTel-compatible and displays nicely in Sentry."""
|
|
1880
|
+
# check for allowed primitives
|
|
1881
|
+
if isinstance(value, (int, str, float, bool)):
|
|
1882
|
+
return value
|
|
1883
|
+
|
|
1884
|
+
# lists are allowed too, as long as they don't mix types
|
|
1885
|
+
if isinstance(value, (list, tuple)):
|
|
1886
|
+
for type_ in (int, str, float, bool):
|
|
1887
|
+
if all(isinstance(item, type_) for item in value):
|
|
1888
|
+
return list(value)
|
|
1889
|
+
|
|
1890
|
+
# if this is anything else, just try to coerce to string
|
|
1891
|
+
# we prefer json.dumps since this makes things like dictionaries display
|
|
1892
|
+
# nicely in the UI
|
|
1893
|
+
try:
|
|
1894
|
+
return json.dumps(value)
|
|
1895
|
+
except TypeError:
|
|
1896
|
+
try:
|
|
1897
|
+
return str(value)
|
|
1898
|
+
except Exception:
|
|
1899
|
+
return None
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
ISO_TZ_SEPARATORS = frozenset(("+", "-"))
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
def datetime_from_isoformat(value):
|
|
1906
|
+
# type: (str) -> datetime
|
|
1907
|
+
try:
|
|
1908
|
+
result = datetime.fromisoformat(value)
|
|
1909
|
+
except (AttributeError, ValueError):
|
|
1910
|
+
# py 3.6
|
|
1911
|
+
timestamp_format = (
|
|
1912
|
+
"%Y-%m-%dT%H:%M:%S.%f" if "." in value else "%Y-%m-%dT%H:%M:%S"
|
|
1913
|
+
)
|
|
1914
|
+
if value.endswith("Z"):
|
|
1915
|
+
value = value[:-1] + "+0000"
|
|
1916
|
+
|
|
1917
|
+
if value[-6] in ISO_TZ_SEPARATORS:
|
|
1918
|
+
timestamp_format += "%z"
|
|
1919
|
+
value = value[:-3] + value[-2:]
|
|
1920
|
+
elif value[-5] in ISO_TZ_SEPARATORS:
|
|
1921
|
+
timestamp_format += "%z"
|
|
1922
|
+
|
|
1923
|
+
result = datetime.strptime(value, timestamp_format)
|
|
1924
|
+
return result.astimezone(timezone.utc)
|
|
1925
|
+
|
|
1926
|
+
|
|
1912
1927
|
def should_be_treated_as_error(ty, value):
|
|
1913
1928
|
# type: (Any, Any) -> bool
|
|
1914
1929
|
if ty == SystemExit and hasattr(value, "code") and value.code in (0, None):
|
|
@@ -1918,18 +1933,20 @@ def should_be_treated_as_error(ty, value):
|
|
|
1918
1933
|
return True
|
|
1919
1934
|
|
|
1920
1935
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1936
|
+
def http_client_status_to_breadcrumb_level(status_code):
|
|
1937
|
+
# type: (Optional[int]) -> str
|
|
1938
|
+
if status_code is not None:
|
|
1939
|
+
if 500 <= status_code <= 599:
|
|
1940
|
+
return "error"
|
|
1941
|
+
elif 400 <= status_code <= 499:
|
|
1942
|
+
return "warning"
|
|
1923
1943
|
|
|
1944
|
+
return "info"
|
|
1924
1945
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
try:
|
|
1933
|
-
return convert_func(value)
|
|
1934
|
-
except Exception:
|
|
1935
|
-
return None
|
|
1946
|
+
|
|
1947
|
+
def set_thread_info_from_span(data, span):
|
|
1948
|
+
# type: (Dict[str, Any], sentry_sdk.tracing.Span) -> None
|
|
1949
|
+
if span.get_attribute(SPANDATA.THREAD_ID) is not None:
|
|
1950
|
+
data[SPANDATA.THREAD_ID] = span.get_attribute(SPANDATA.THREAD_ID)
|
|
1951
|
+
if span.get_attribute(SPANDATA.THREAD_NAME) is not None:
|
|
1952
|
+
data[SPANDATA.THREAD_NAME] = span.get_attribute(SPANDATA.THREAD_NAME)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sentry-sdk
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0a2
|
|
4
4
|
Summary: Python client for Sentry (https://sentry.io)
|
|
5
5
|
Home-page: https://github.com/getsentry/sentry-python
|
|
6
6
|
Author: Sentry Team and Contributors
|
|
@@ -15,7 +15,6 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.7
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -24,11 +23,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
26
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.7
|
|
28
27
|
Description-Content-Type: text/markdown
|
|
29
28
|
License-File: LICENSE
|
|
30
29
|
Requires-Dist: urllib3>=1.26.11
|
|
31
30
|
Requires-Dist: certifi
|
|
31
|
+
Requires-Dist: opentelemetry-sdk>=1.4.0
|
|
32
32
|
Provides-Extra: aiohttp
|
|
33
33
|
Requires-Dist: aiohttp>=3.5; extra == "aiohttp"
|
|
34
34
|
Provides-Extra: anthropic
|
|
@@ -85,8 +85,6 @@ Provides-Extra: openfeature
|
|
|
85
85
|
Requires-Dist: openfeature-sdk>=0.7.1; extra == "openfeature"
|
|
86
86
|
Provides-Extra: opentelemetry
|
|
87
87
|
Requires-Dist: opentelemetry-distro>=0.35b0; extra == "opentelemetry"
|
|
88
|
-
Provides-Extra: opentelemetry-experimental
|
|
89
|
-
Requires-Dist: opentelemetry-distro; extra == "opentelemetry-experimental"
|
|
90
88
|
Provides-Extra: pure-eval
|
|
91
89
|
Requires-Dist: pure_eval; extra == "pure-eval"
|
|
92
90
|
Requires-Dist: executing; extra == "pure-eval"
|