sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__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 +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
sentry_sdk/scrubber.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from sentry_sdk.utils import (
|
|
2
3
|
capture_internal_exceptions,
|
|
3
4
|
AnnotatedValue,
|
|
4
5
|
iter_event_frames,
|
|
5
6
|
)
|
|
6
7
|
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
11
|
+
from typing import List, Optional
|
|
10
12
|
from sentry_sdk._types import Event
|
|
11
|
-
from typing import Optional
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
DEFAULT_DENYLIST = [
|
|
@@ -60,9 +61,12 @@ DEFAULT_PII_DENYLIST = [
|
|
|
60
61
|
|
|
61
62
|
class EventScrubber:
|
|
62
63
|
def __init__(
|
|
63
|
-
self,
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
self,
|
|
65
|
+
denylist: Optional[List[str]] = None,
|
|
66
|
+
recursive: bool = False,
|
|
67
|
+
send_default_pii: bool = False,
|
|
68
|
+
pii_denylist: Optional[List[str]] = None,
|
|
69
|
+
) -> None:
|
|
66
70
|
"""
|
|
67
71
|
A scrubber that goes through the event payload and removes sensitive data configured through denylists.
|
|
68
72
|
|
|
@@ -82,8 +86,7 @@ class EventScrubber:
|
|
|
82
86
|
self.denylist = [x.lower() for x in self.denylist]
|
|
83
87
|
self.recursive = recursive
|
|
84
88
|
|
|
85
|
-
def scrub_list(self, lst):
|
|
86
|
-
# type: (object) -> None
|
|
89
|
+
def scrub_list(self, lst: object) -> None:
|
|
87
90
|
"""
|
|
88
91
|
If a list is passed to this method, the method recursively searches the list and any
|
|
89
92
|
nested lists for any dictionaries. The method calls scrub_dict on all dictionaries
|
|
@@ -97,8 +100,7 @@ class EventScrubber:
|
|
|
97
100
|
self.scrub_dict(v) # no-op unless v is a dict
|
|
98
101
|
self.scrub_list(v) # no-op unless v is a list
|
|
99
102
|
|
|
100
|
-
def scrub_dict(self, d):
|
|
101
|
-
# type: (object) -> None
|
|
103
|
+
def scrub_dict(self, d: object) -> None:
|
|
102
104
|
"""
|
|
103
105
|
If a dictionary is passed to this method, the method scrubs the dictionary of any
|
|
104
106
|
sensitive data. The method calls itself recursively on any nested dictionaries (
|
|
@@ -117,8 +119,7 @@ class EventScrubber:
|
|
|
117
119
|
self.scrub_dict(v) # no-op unless v is a dict
|
|
118
120
|
self.scrub_list(v) # no-op unless v is a list
|
|
119
121
|
|
|
120
|
-
def scrub_request(self, event):
|
|
121
|
-
# type: (Event) -> None
|
|
122
|
+
def scrub_request(self, event: Event) -> None:
|
|
122
123
|
with capture_internal_exceptions():
|
|
123
124
|
if "request" in event:
|
|
124
125
|
if "headers" in event["request"]:
|
|
@@ -128,20 +129,17 @@ class EventScrubber:
|
|
|
128
129
|
if "data" in event["request"]:
|
|
129
130
|
self.scrub_dict(event["request"]["data"])
|
|
130
131
|
|
|
131
|
-
def scrub_extra(self, event):
|
|
132
|
-
# type: (Event) -> None
|
|
132
|
+
def scrub_extra(self, event: Event) -> None:
|
|
133
133
|
with capture_internal_exceptions():
|
|
134
134
|
if "extra" in event:
|
|
135
135
|
self.scrub_dict(event["extra"])
|
|
136
136
|
|
|
137
|
-
def scrub_user(self, event):
|
|
138
|
-
# type: (Event) -> None
|
|
137
|
+
def scrub_user(self, event: Event) -> None:
|
|
139
138
|
with capture_internal_exceptions():
|
|
140
139
|
if "user" in event:
|
|
141
140
|
self.scrub_dict(event["user"])
|
|
142
141
|
|
|
143
|
-
def scrub_breadcrumbs(self, event):
|
|
144
|
-
# type: (Event) -> None
|
|
142
|
+
def scrub_breadcrumbs(self, event: Event) -> None:
|
|
145
143
|
with capture_internal_exceptions():
|
|
146
144
|
if "breadcrumbs" in event:
|
|
147
145
|
if (
|
|
@@ -152,23 +150,21 @@ class EventScrubber:
|
|
|
152
150
|
if "data" in value:
|
|
153
151
|
self.scrub_dict(value["data"])
|
|
154
152
|
|
|
155
|
-
def scrub_frames(self, event):
|
|
156
|
-
# type: (Event) -> None
|
|
153
|
+
def scrub_frames(self, event: Event) -> None:
|
|
157
154
|
with capture_internal_exceptions():
|
|
158
155
|
for frame in iter_event_frames(event):
|
|
159
156
|
if "vars" in frame:
|
|
160
157
|
self.scrub_dict(frame["vars"])
|
|
161
158
|
|
|
162
|
-
def scrub_spans(self, event):
|
|
163
|
-
# type: (Event) -> None
|
|
159
|
+
def scrub_spans(self, event: Event) -> None:
|
|
164
160
|
with capture_internal_exceptions():
|
|
165
161
|
if "spans" in event:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
162
|
+
if not isinstance(event["spans"], AnnotatedValue):
|
|
163
|
+
for span in event["spans"]:
|
|
164
|
+
if "data" in span:
|
|
165
|
+
self.scrub_dict(span["data"])
|
|
169
166
|
|
|
170
|
-
def scrub_event(self, event):
|
|
171
|
-
# type: (Event) -> None
|
|
167
|
+
def scrub_event(self, event: Event) -> None:
|
|
172
168
|
self.scrub_request(event)
|
|
173
169
|
self.scrub_extra(event)
|
|
174
170
|
self.scrub_user(event)
|
sentry_sdk/serializer.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import sys
|
|
2
3
|
import math
|
|
3
4
|
from collections.abc import Mapping, Sequence, Set
|
|
@@ -26,7 +27,7 @@ if TYPE_CHECKING:
|
|
|
26
27
|
from typing import Type
|
|
27
28
|
from typing import Union
|
|
28
29
|
|
|
29
|
-
from sentry_sdk._types import NotImplementedType
|
|
30
|
+
from sentry_sdk._types import NotImplementedType, Event
|
|
30
31
|
|
|
31
32
|
Span = Dict[str, Any]
|
|
32
33
|
|
|
@@ -55,29 +56,25 @@ MAX_DATABAG_BREADTH = 10
|
|
|
55
56
|
CYCLE_MARKER = "<cyclic>"
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
global_repr_processors
|
|
59
|
+
global_repr_processors: List[ReprProcessor] = []
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
def add_global_repr_processor(processor):
|
|
62
|
-
# type: (ReprProcessor) -> None
|
|
62
|
+
def add_global_repr_processor(processor: ReprProcessor) -> None:
|
|
63
63
|
global_repr_processors.append(processor)
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class Memo:
|
|
67
67
|
__slots__ = ("_ids", "_objs")
|
|
68
68
|
|
|
69
|
-
def __init__(self):
|
|
70
|
-
|
|
71
|
-
self.
|
|
72
|
-
self._objs = [] # type: List[Any]
|
|
69
|
+
def __init__(self) -> None:
|
|
70
|
+
self._ids: Dict[int, Any] = {}
|
|
71
|
+
self._objs: List[Any] = []
|
|
73
72
|
|
|
74
|
-
def memoize(self, obj):
|
|
75
|
-
# type: (Any) -> ContextManager[bool]
|
|
73
|
+
def memoize(self, obj: Any) -> ContextManager[bool]:
|
|
76
74
|
self._objs.append(obj)
|
|
77
75
|
return self
|
|
78
76
|
|
|
79
|
-
def __enter__(self):
|
|
80
|
-
# type: () -> bool
|
|
77
|
+
def __enter__(self) -> bool:
|
|
81
78
|
obj = self._objs[-1]
|
|
82
79
|
if id(obj) in self._ids:
|
|
83
80
|
return True
|
|
@@ -87,16 +84,14 @@ class Memo:
|
|
|
87
84
|
|
|
88
85
|
def __exit__(
|
|
89
86
|
self,
|
|
90
|
-
ty
|
|
91
|
-
value
|
|
92
|
-
tb
|
|
93
|
-
):
|
|
94
|
-
# type: (...) -> None
|
|
87
|
+
ty: Optional[Type[BaseException]],
|
|
88
|
+
value: Optional[BaseException],
|
|
89
|
+
tb: Optional[TracebackType],
|
|
90
|
+
) -> None:
|
|
95
91
|
self._ids.pop(id(self._objs.pop()), None)
|
|
96
92
|
|
|
97
93
|
|
|
98
|
-
def serialize(event, **kwargs):
|
|
99
|
-
# type: (Dict[str, Any], **Any) -> Dict[str, Any]
|
|
94
|
+
def serialize(event: Union[Dict[str, Any], Event], **kwargs: Any) -> Dict[str, Any]:
|
|
100
95
|
"""
|
|
101
96
|
A very smart serializer that takes a dict and emits a json-friendly dict.
|
|
102
97
|
Currently used for serializing the final Event and also prematurely while fetching the stack
|
|
@@ -117,18 +112,15 @@ def serialize(event, **kwargs):
|
|
|
117
112
|
|
|
118
113
|
"""
|
|
119
114
|
memo = Memo()
|
|
120
|
-
path
|
|
121
|
-
meta_stack
|
|
115
|
+
path: List[Segment] = []
|
|
116
|
+
meta_stack: List[Dict[str, Any]] = []
|
|
122
117
|
|
|
123
|
-
keep_request_bodies = (
|
|
124
|
-
|
|
125
|
-
) # type: bool
|
|
126
|
-
max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int]
|
|
118
|
+
keep_request_bodies: bool = kwargs.pop("max_request_body_size", None) == "always"
|
|
119
|
+
max_value_length: Optional[int] = kwargs.pop("max_value_length", None)
|
|
127
120
|
is_vars = kwargs.pop("is_vars", False)
|
|
128
|
-
custom_repr = kwargs.pop("custom_repr", None)
|
|
121
|
+
custom_repr: Callable[..., Optional[str]] = kwargs.pop("custom_repr", None)
|
|
129
122
|
|
|
130
|
-
def _safe_repr_wrapper(value):
|
|
131
|
-
# type: (Any) -> str
|
|
123
|
+
def _safe_repr_wrapper(value: Any) -> str:
|
|
132
124
|
try:
|
|
133
125
|
repr_value = None
|
|
134
126
|
if custom_repr is not None:
|
|
@@ -137,8 +129,7 @@ def serialize(event, **kwargs):
|
|
|
137
129
|
except Exception:
|
|
138
130
|
return safe_repr(value)
|
|
139
131
|
|
|
140
|
-
def _annotate(**meta):
|
|
141
|
-
# type: (**Any) -> None
|
|
132
|
+
def _annotate(**meta: Any) -> None:
|
|
142
133
|
while len(meta_stack) <= len(path):
|
|
143
134
|
try:
|
|
144
135
|
segment = path[len(meta_stack) - 1]
|
|
@@ -150,8 +141,7 @@ def serialize(event, **kwargs):
|
|
|
150
141
|
|
|
151
142
|
meta_stack[-1].setdefault("", {}).update(meta)
|
|
152
143
|
|
|
153
|
-
def _is_databag():
|
|
154
|
-
# type: () -> Optional[bool]
|
|
144
|
+
def _is_databag() -> Optional[bool]:
|
|
155
145
|
"""
|
|
156
146
|
A databag is any value that we need to trim.
|
|
157
147
|
True for stuff like vars, request bodies, breadcrumbs and extra.
|
|
@@ -179,8 +169,7 @@ def serialize(event, **kwargs):
|
|
|
179
169
|
|
|
180
170
|
return False
|
|
181
171
|
|
|
182
|
-
def _is_request_body():
|
|
183
|
-
# type: () -> Optional[bool]
|
|
172
|
+
def _is_request_body() -> Optional[bool]:
|
|
184
173
|
try:
|
|
185
174
|
if path[0] == "request" and path[1] == "data":
|
|
186
175
|
return True
|
|
@@ -190,15 +179,14 @@ def serialize(event, **kwargs):
|
|
|
190
179
|
return False
|
|
191
180
|
|
|
192
181
|
def _serialize_node(
|
|
193
|
-
obj
|
|
194
|
-
is_databag
|
|
195
|
-
is_request_body
|
|
196
|
-
should_repr_strings
|
|
197
|
-
segment
|
|
198
|
-
remaining_breadth
|
|
199
|
-
remaining_depth
|
|
200
|
-
):
|
|
201
|
-
# type: (...) -> Any
|
|
182
|
+
obj: Any,
|
|
183
|
+
is_databag: Optional[bool] = None,
|
|
184
|
+
is_request_body: Optional[bool] = None,
|
|
185
|
+
should_repr_strings: Optional[bool] = None,
|
|
186
|
+
segment: Optional[Segment] = None,
|
|
187
|
+
remaining_breadth: Optional[Union[int, float]] = None,
|
|
188
|
+
remaining_depth: Optional[Union[int, float]] = None,
|
|
189
|
+
) -> Any:
|
|
202
190
|
if segment is not None:
|
|
203
191
|
path.append(segment)
|
|
204
192
|
|
|
@@ -227,22 +215,20 @@ def serialize(event, **kwargs):
|
|
|
227
215
|
path.pop()
|
|
228
216
|
del meta_stack[len(path) + 1 :]
|
|
229
217
|
|
|
230
|
-
def _flatten_annotated(obj):
|
|
231
|
-
# type: (Any) -> Any
|
|
218
|
+
def _flatten_annotated(obj: Any) -> Any:
|
|
232
219
|
if isinstance(obj, AnnotatedValue):
|
|
233
220
|
_annotate(**obj.metadata)
|
|
234
221
|
obj = obj.value
|
|
235
222
|
return obj
|
|
236
223
|
|
|
237
224
|
def _serialize_node_impl(
|
|
238
|
-
obj,
|
|
239
|
-
is_databag,
|
|
240
|
-
is_request_body,
|
|
241
|
-
should_repr_strings,
|
|
242
|
-
remaining_depth,
|
|
243
|
-
remaining_breadth,
|
|
244
|
-
):
|
|
245
|
-
# type: (Any, Optional[bool], Optional[bool], Optional[bool], Optional[Union[float, int]], Optional[Union[float, int]]) -> Any
|
|
225
|
+
obj: Any,
|
|
226
|
+
is_databag: Optional[bool],
|
|
227
|
+
is_request_body: Optional[bool],
|
|
228
|
+
should_repr_strings: Optional[bool],
|
|
229
|
+
remaining_depth: Optional[Union[float, int]],
|
|
230
|
+
remaining_breadth: Optional[Union[float, int]],
|
|
231
|
+
) -> Any:
|
|
246
232
|
if isinstance(obj, AnnotatedValue):
|
|
247
233
|
should_repr_strings = False
|
|
248
234
|
if should_repr_strings is None:
|
|
@@ -306,7 +292,7 @@ def serialize(event, **kwargs):
|
|
|
306
292
|
# might mutate our dictionary while we're still iterating over it.
|
|
307
293
|
obj = dict(obj.items())
|
|
308
294
|
|
|
309
|
-
rv_dict
|
|
295
|
+
rv_dict: Dict[str, Any] = {}
|
|
310
296
|
i = 0
|
|
311
297
|
|
|
312
298
|
for k, v in obj.items():
|
sentry_sdk/session.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import uuid
|
|
2
3
|
from datetime import datetime, timezone
|
|
3
4
|
|
|
@@ -6,23 +7,15 @@ from sentry_sdk.utils import format_timestamp
|
|
|
6
7
|
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
|
-
from typing import Optional
|
|
10
|
-
from typing import Union
|
|
11
|
-
from typing import Any
|
|
12
|
-
from typing import Dict
|
|
13
|
-
|
|
14
10
|
from sentry_sdk._types import SessionStatus
|
|
11
|
+
from typing import Optional, Union, Any, Dict
|
|
15
12
|
|
|
16
13
|
|
|
17
|
-
def _minute_trunc(ts):
|
|
18
|
-
# type: (datetime) -> datetime
|
|
14
|
+
def _minute_trunc(ts: datetime) -> datetime:
|
|
19
15
|
return ts.replace(second=0, microsecond=0)
|
|
20
16
|
|
|
21
17
|
|
|
22
|
-
def _make_uuid(
|
|
23
|
-
val, # type: Union[str, uuid.UUID]
|
|
24
|
-
):
|
|
25
|
-
# type: (...) -> uuid.UUID
|
|
18
|
+
def _make_uuid(val: Union[str, uuid.UUID]) -> uuid.UUID:
|
|
26
19
|
if isinstance(val, uuid.UUID):
|
|
27
20
|
return val
|
|
28
21
|
return uuid.UUID(val)
|
|
@@ -31,21 +24,20 @@ def _make_uuid(
|
|
|
31
24
|
class Session:
|
|
32
25
|
def __init__(
|
|
33
26
|
self,
|
|
34
|
-
sid
|
|
35
|
-
did
|
|
36
|
-
timestamp
|
|
37
|
-
started
|
|
38
|
-
duration
|
|
39
|
-
status
|
|
40
|
-
release
|
|
41
|
-
environment
|
|
42
|
-
user_agent
|
|
43
|
-
ip_address
|
|
44
|
-
errors
|
|
45
|
-
user
|
|
46
|
-
session_mode="application",
|
|
47
|
-
):
|
|
48
|
-
# type: (...) -> None
|
|
27
|
+
sid: Optional[Union[str, uuid.UUID]] = None,
|
|
28
|
+
did: Optional[str] = None,
|
|
29
|
+
timestamp: Optional[datetime] = None,
|
|
30
|
+
started: Optional[datetime] = None,
|
|
31
|
+
duration: Optional[float] = None,
|
|
32
|
+
status: Optional[SessionStatus] = None,
|
|
33
|
+
release: Optional[str] = None,
|
|
34
|
+
environment: Optional[str] = None,
|
|
35
|
+
user_agent: Optional[str] = None,
|
|
36
|
+
ip_address: Optional[str] = None,
|
|
37
|
+
errors: Optional[int] = None,
|
|
38
|
+
user: Optional[Any] = None,
|
|
39
|
+
session_mode: str = "application",
|
|
40
|
+
) -> None:
|
|
49
41
|
if sid is None:
|
|
50
42
|
sid = uuid.uuid4()
|
|
51
43
|
if started is None:
|
|
@@ -53,14 +45,14 @@ class Session:
|
|
|
53
45
|
if status is None:
|
|
54
46
|
status = "ok"
|
|
55
47
|
self.status = status
|
|
56
|
-
self.did
|
|
48
|
+
self.did: Optional[str] = None
|
|
57
49
|
self.started = started
|
|
58
|
-
self.release
|
|
59
|
-
self.environment
|
|
60
|
-
self.duration
|
|
61
|
-
self.user_agent
|
|
62
|
-
self.ip_address
|
|
63
|
-
self.session_mode = session_mode
|
|
50
|
+
self.release: Optional[str] = None
|
|
51
|
+
self.environment: Optional[str] = None
|
|
52
|
+
self.duration: Optional[float] = None
|
|
53
|
+
self.user_agent: Optional[str] = None
|
|
54
|
+
self.ip_address: Optional[str] = None
|
|
55
|
+
self.session_mode: str = session_mode
|
|
64
56
|
self.errors = 0
|
|
65
57
|
|
|
66
58
|
self.update(
|
|
@@ -77,26 +69,24 @@ class Session:
|
|
|
77
69
|
)
|
|
78
70
|
|
|
79
71
|
@property
|
|
80
|
-
def truncated_started(self):
|
|
81
|
-
# type: (...) -> datetime
|
|
72
|
+
def truncated_started(self) -> datetime:
|
|
82
73
|
return _minute_trunc(self.started)
|
|
83
74
|
|
|
84
75
|
def update(
|
|
85
76
|
self,
|
|
86
|
-
sid
|
|
87
|
-
did
|
|
88
|
-
timestamp
|
|
89
|
-
started
|
|
90
|
-
duration
|
|
91
|
-
status
|
|
92
|
-
release
|
|
93
|
-
environment
|
|
94
|
-
user_agent
|
|
95
|
-
ip_address
|
|
96
|
-
errors
|
|
97
|
-
user
|
|
98
|
-
):
|
|
99
|
-
# type: (...) -> None
|
|
77
|
+
sid: Optional[Union[str, uuid.UUID]] = None,
|
|
78
|
+
did: Optional[str] = None,
|
|
79
|
+
timestamp: Optional[datetime] = None,
|
|
80
|
+
started: Optional[datetime] = None,
|
|
81
|
+
duration: Optional[float] = None,
|
|
82
|
+
status: Optional[SessionStatus] = None,
|
|
83
|
+
release: Optional[str] = None,
|
|
84
|
+
environment: Optional[str] = None,
|
|
85
|
+
user_agent: Optional[str] = None,
|
|
86
|
+
ip_address: Optional[str] = None,
|
|
87
|
+
errors: Optional[int] = None,
|
|
88
|
+
user: Optional[Any] = None,
|
|
89
|
+
) -> None:
|
|
100
90
|
# If a user is supplied we pull some data form it
|
|
101
91
|
if user:
|
|
102
92
|
if ip_address is None:
|
|
@@ -129,19 +119,13 @@ class Session:
|
|
|
129
119
|
if status is not None:
|
|
130
120
|
self.status = status
|
|
131
121
|
|
|
132
|
-
def close(
|
|
133
|
-
self, status=None # type: Optional[SessionStatus]
|
|
134
|
-
):
|
|
135
|
-
# type: (...) -> Any
|
|
122
|
+
def close(self, status: Optional[SessionStatus] = None) -> Any:
|
|
136
123
|
if status is None and self.status == "ok":
|
|
137
124
|
status = "exited"
|
|
138
125
|
if status is not None:
|
|
139
126
|
self.update(status=status)
|
|
140
127
|
|
|
141
|
-
def get_json_attrs(
|
|
142
|
-
self, with_user_info=True # type: Optional[bool]
|
|
143
|
-
):
|
|
144
|
-
# type: (...) -> Any
|
|
128
|
+
def get_json_attrs(self, with_user_info: bool = True) -> Any:
|
|
145
129
|
attrs = {}
|
|
146
130
|
if self.release is not None:
|
|
147
131
|
attrs["release"] = self.release
|
|
@@ -154,15 +138,14 @@ class Session:
|
|
|
154
138
|
attrs["user_agent"] = self.user_agent
|
|
155
139
|
return attrs
|
|
156
140
|
|
|
157
|
-
def to_json(self):
|
|
158
|
-
|
|
159
|
-
rv = {
|
|
141
|
+
def to_json(self) -> Any:
|
|
142
|
+
rv: Dict[str, Any] = {
|
|
160
143
|
"sid": str(self.sid),
|
|
161
144
|
"init": True,
|
|
162
145
|
"started": format_timestamp(self.started),
|
|
163
146
|
"timestamp": format_timestamp(self.timestamp),
|
|
164
147
|
"status": self.status,
|
|
165
|
-
}
|
|
148
|
+
}
|
|
166
149
|
if self.errors:
|
|
167
150
|
rv["errors"] = self.errors
|
|
168
151
|
if self.did is not None:
|
sentry_sdk/sessions.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import os
|
|
2
|
-
import
|
|
3
|
-
from threading import Thread, Lock
|
|
3
|
+
from threading import Thread, Lock, Event
|
|
4
4
|
from contextlib import contextmanager
|
|
5
5
|
|
|
6
6
|
import sentry_sdk
|
|
@@ -11,16 +11,17 @@ from sentry_sdk.utils import format_timestamp
|
|
|
11
11
|
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from typing import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
from typing import (
|
|
15
|
+
Any,
|
|
16
|
+
Callable,
|
|
17
|
+
Dict,
|
|
18
|
+
List,
|
|
19
|
+
Optional,
|
|
20
|
+
Generator,
|
|
21
|
+
)
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def _is_auto_session_tracking_enabled(scope):
|
|
23
|
-
# type: (sentry_sdk.Scope) -> bool
|
|
24
|
+
def _is_auto_session_tracking_enabled(scope: sentry_sdk.Scope) -> bool:
|
|
24
25
|
"""
|
|
25
26
|
Utility function to find out if session tracking is enabled.
|
|
26
27
|
"""
|
|
@@ -34,8 +35,9 @@ def _is_auto_session_tracking_enabled(scope):
|
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
@contextmanager
|
|
37
|
-
def track_session(
|
|
38
|
-
|
|
38
|
+
def track_session(
|
|
39
|
+
scope: sentry_sdk.Scope, session_mode: str = "application"
|
|
40
|
+
) -> Generator[None, None, None]:
|
|
39
41
|
"""
|
|
40
42
|
Start a new session in the provided scope, assuming session tracking is enabled.
|
|
41
43
|
This is a no-op context manager if session tracking is not enabled.
|
|
@@ -55,30 +57,27 @@ TERMINAL_SESSION_STATES = ("exited", "abnormal", "crashed")
|
|
|
55
57
|
MAX_ENVELOPE_ITEMS = 100
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
def make_aggregate_envelope(aggregate_states, attrs):
|
|
59
|
-
# type: (Any, Any) -> Any
|
|
60
|
+
def make_aggregate_envelope(aggregate_states: Any, attrs: Any) -> Any:
|
|
60
61
|
return {"attrs": dict(attrs), "aggregates": list(aggregate_states.values())}
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
class SessionFlusher:
|
|
64
65
|
def __init__(
|
|
65
66
|
self,
|
|
66
|
-
capture_func
|
|
67
|
-
flush_interval=60,
|
|
68
|
-
):
|
|
69
|
-
# type: (...) -> None
|
|
67
|
+
capture_func: Callable[[Envelope], None],
|
|
68
|
+
flush_interval: int = 60,
|
|
69
|
+
) -> None:
|
|
70
70
|
self.capture_func = capture_func
|
|
71
71
|
self.flush_interval = flush_interval
|
|
72
|
-
self.pending_sessions
|
|
73
|
-
self.pending_aggregates
|
|
74
|
-
self._thread
|
|
72
|
+
self.pending_sessions: List[Any] = []
|
|
73
|
+
self.pending_aggregates: Dict[Any, Any] = {}
|
|
74
|
+
self._thread: Optional[Thread] = None
|
|
75
75
|
self._thread_lock = Lock()
|
|
76
76
|
self._aggregate_lock = Lock()
|
|
77
|
-
self._thread_for_pid
|
|
78
|
-
self.
|
|
77
|
+
self._thread_for_pid: Optional[int] = None
|
|
78
|
+
self.__shutdown_requested: Event = Event()
|
|
79
79
|
|
|
80
|
-
def flush(self):
|
|
81
|
-
# type: (...) -> None
|
|
80
|
+
def flush(self) -> None:
|
|
82
81
|
pending_sessions = self.pending_sessions
|
|
83
82
|
self.pending_sessions = []
|
|
84
83
|
|
|
@@ -104,8 +103,7 @@ class SessionFlusher:
|
|
|
104
103
|
if len(envelope.items) > 0:
|
|
105
104
|
self.capture_func(envelope)
|
|
106
105
|
|
|
107
|
-
def _ensure_running(self):
|
|
108
|
-
# type: (...) -> None
|
|
106
|
+
def _ensure_running(self) -> None:
|
|
109
107
|
"""
|
|
110
108
|
Check that we have an active thread to run in, or create one if not.
|
|
111
109
|
|
|
@@ -119,12 +117,11 @@ class SessionFlusher:
|
|
|
119
117
|
if self._thread_for_pid == os.getpid() and self._thread is not None:
|
|
120
118
|
return None
|
|
121
119
|
|
|
122
|
-
def _thread():
|
|
123
|
-
|
|
124
|
-
while
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
self.flush()
|
|
120
|
+
def _thread() -> None:
|
|
121
|
+
running = True
|
|
122
|
+
while running:
|
|
123
|
+
running = not self.__shutdown_requested.wait(self.flush_interval)
|
|
124
|
+
self.flush()
|
|
128
125
|
|
|
129
126
|
thread = Thread(target=_thread)
|
|
130
127
|
thread.daemon = True
|
|
@@ -133,7 +130,7 @@ class SessionFlusher:
|
|
|
133
130
|
except RuntimeError:
|
|
134
131
|
# Unfortunately at this point the interpreter is in a state that no
|
|
135
132
|
# longer allows us to spawn a thread and we have to bail.
|
|
136
|
-
self.
|
|
133
|
+
self.__shutdown_requested.set()
|
|
137
134
|
return None
|
|
138
135
|
|
|
139
136
|
self._thread = thread
|
|
@@ -141,10 +138,7 @@ class SessionFlusher:
|
|
|
141
138
|
|
|
142
139
|
return None
|
|
143
140
|
|
|
144
|
-
def add_aggregate_session(
|
|
145
|
-
self, session # type: Session
|
|
146
|
-
):
|
|
147
|
-
# type: (...) -> None
|
|
141
|
+
def add_aggregate_session(self, session: Session) -> None:
|
|
148
142
|
# NOTE on `session.did`:
|
|
149
143
|
# the protocol can deal with buckets that have a distinct-id, however
|
|
150
144
|
# in practice we expect the python SDK to have an extremely high cardinality
|
|
@@ -172,20 +166,12 @@ class SessionFlusher:
|
|
|
172
166
|
else:
|
|
173
167
|
state["exited"] = state.get("exited", 0) + 1
|
|
174
168
|
|
|
175
|
-
def add_session(
|
|
176
|
-
self, session # type: Session
|
|
177
|
-
):
|
|
178
|
-
# type: (...) -> None
|
|
169
|
+
def add_session(self, session: Session) -> None:
|
|
179
170
|
if session.session_mode == "request":
|
|
180
171
|
self.add_aggregate_session(session)
|
|
181
172
|
else:
|
|
182
173
|
self.pending_sessions.append(session.to_json())
|
|
183
174
|
self._ensure_running()
|
|
184
175
|
|
|
185
|
-
def kill(self):
|
|
186
|
-
|
|
187
|
-
self._running = False
|
|
188
|
-
|
|
189
|
-
def __del__(self):
|
|
190
|
-
# type: (...) -> None
|
|
191
|
-
self.kill()
|
|
176
|
+
def kill(self) -> None:
|
|
177
|
+
self.__shutdown_requested.set()
|