sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.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.
- sentry_sdk/__init__.py +48 -6
- sentry_sdk/_compat.py +64 -56
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +81 -19
- sentry_sdk/_types.py +311 -11
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +35 -28
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +20 -11
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +71 -60
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +126 -125
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
sentry_sdk/serializer.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import math
|
|
3
|
-
|
|
3
|
+
from collections.abc import Mapping, Sequence, Set
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
6
|
from sentry_sdk.utils import (
|
|
@@ -8,20 +8,13 @@ from sentry_sdk.utils import (
|
|
|
8
8
|
capture_internal_exception,
|
|
9
9
|
disable_capture_event,
|
|
10
10
|
format_timestamp,
|
|
11
|
-
json_dumps,
|
|
12
11
|
safe_repr,
|
|
13
12
|
strip_string,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
from sentry_sdk._compat import text_type, PY2, string_types, number_types, iteritems
|
|
19
|
-
|
|
20
|
-
from sentry_sdk._types import MYPY
|
|
21
|
-
|
|
22
|
-
if MYPY:
|
|
23
|
-
from datetime import timedelta
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
24
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
25
18
|
from types import TracebackType
|
|
26
19
|
|
|
27
20
|
from typing import Any
|
|
@@ -30,11 +23,10 @@ if MYPY:
|
|
|
30
23
|
from typing import Dict
|
|
31
24
|
from typing import List
|
|
32
25
|
from typing import Optional
|
|
33
|
-
from typing import Tuple
|
|
34
26
|
from typing import Type
|
|
35
27
|
from typing import Union
|
|
36
28
|
|
|
37
|
-
from sentry_sdk._types import NotImplementedType
|
|
29
|
+
from sentry_sdk._types import NotImplementedType
|
|
38
30
|
|
|
39
31
|
Span = Dict[str, Any]
|
|
40
32
|
|
|
@@ -42,20 +34,8 @@ if MYPY:
|
|
|
42
34
|
Segment = Union[str, int]
|
|
43
35
|
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# https://github.com/python/cpython/blob/master/Lib/collections/__init__.py#L49
|
|
48
|
-
from collections import Mapping, Sequence, Set
|
|
49
|
-
|
|
50
|
-
serializable_str_types = string_types
|
|
51
|
-
|
|
52
|
-
else:
|
|
53
|
-
# New in 3.3
|
|
54
|
-
# https://docs.python.org/3/library/collections.abc.html
|
|
55
|
-
from collections.abc import Mapping, Sequence, Set
|
|
56
|
-
|
|
57
|
-
# Bytes are technically not strings in Python 3, but we can serialize them
|
|
58
|
-
serializable_str_types = (str, bytes)
|
|
37
|
+
# Bytes are technically not strings in Python 3, but we can serialize them
|
|
38
|
+
serializable_str_types = (str, bytes, bytearray, memoryview)
|
|
59
39
|
|
|
60
40
|
|
|
61
41
|
# Maximum length of JSON-serialized event payloads that can be safely sent
|
|
@@ -66,11 +46,13 @@ else:
|
|
|
66
46
|
# Can be overwritten if wanting to send more bytes, e.g. with a custom server.
|
|
67
47
|
# When changing this, keep in mind that events may be a little bit larger than
|
|
68
48
|
# this value due to attached metadata, so keep the number conservative.
|
|
69
|
-
MAX_EVENT_BYTES = 10
|
|
49
|
+
MAX_EVENT_BYTES = 10**6
|
|
70
50
|
|
|
51
|
+
# Maximum depth and breadth of databags. Excess data will be trimmed. If
|
|
52
|
+
# max_request_body_size is "always", request bodies won't be trimmed.
|
|
71
53
|
MAX_DATABAG_DEPTH = 5
|
|
72
54
|
MAX_DATABAG_BREADTH = 10
|
|
73
|
-
CYCLE_MARKER =
|
|
55
|
+
CYCLE_MARKER = "<cyclic>"
|
|
74
56
|
|
|
75
57
|
|
|
76
58
|
global_repr_processors = [] # type: List[ReprProcessor]
|
|
@@ -81,7 +63,15 @@ def add_global_repr_processor(processor):
|
|
|
81
63
|
global_repr_processors.append(processor)
|
|
82
64
|
|
|
83
65
|
|
|
84
|
-
|
|
66
|
+
sequence_types = [Sequence, Set] # type: List[type]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def add_repr_sequence_type(ty):
|
|
70
|
+
# type: (type) -> None
|
|
71
|
+
sequence_types.append(ty)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Memo:
|
|
85
75
|
__slots__ = ("_ids", "_objs")
|
|
86
76
|
|
|
87
77
|
def __init__(self):
|
|
@@ -113,19 +103,52 @@ class Memo(object):
|
|
|
113
103
|
self._ids.pop(id(self._objs.pop()), None)
|
|
114
104
|
|
|
115
105
|
|
|
116
|
-
def serialize(event,
|
|
117
|
-
# type: (
|
|
106
|
+
def serialize(event, **kwargs):
|
|
107
|
+
# type: (Dict[str, Any], **Any) -> Dict[str, Any]
|
|
108
|
+
"""
|
|
109
|
+
A very smart serializer that takes a dict and emits a json-friendly dict.
|
|
110
|
+
Currently used for serializing the final Event and also prematurely while fetching the stack
|
|
111
|
+
local variables for each frame in a stacktrace.
|
|
112
|
+
|
|
113
|
+
It works internally with 'databags' which are arbitrary data structures like Mapping, Sequence and Set.
|
|
114
|
+
The algorithm itself is a recursive graph walk down the data structures it encounters.
|
|
115
|
+
|
|
116
|
+
It has the following responsibilities:
|
|
117
|
+
* Trimming databags and keeping them within MAX_DATABAG_BREADTH and MAX_DATABAG_DEPTH.
|
|
118
|
+
* Calling safe_repr() on objects appropriately to keep them informative and readable in the final payload.
|
|
119
|
+
* Annotating the payload with the _meta field whenever trimming happens.
|
|
120
|
+
|
|
121
|
+
:param max_request_body_size: If set to "always", will never trim request bodies.
|
|
122
|
+
:param max_value_length: The max length to strip strings to, defaults to sentry_sdk.consts.DEFAULT_MAX_VALUE_LENGTH
|
|
123
|
+
:param is_vars: If we're serializing vars early, we want to repr() things that are JSON-serializable to make their type more apparent. For example, it's useful to see the difference between a unicode-string and a bytestring when viewing a stacktrace.
|
|
124
|
+
:param custom_repr: A custom repr function that runs before safe_repr on the object to be serialized. If it returns None or throws internally, we will fallback to safe_repr.
|
|
125
|
+
|
|
126
|
+
"""
|
|
118
127
|
memo = Memo()
|
|
119
128
|
path = [] # type: List[Segment]
|
|
120
129
|
meta_stack = [] # type: List[Dict[str, Any]]
|
|
121
|
-
|
|
130
|
+
|
|
131
|
+
keep_request_bodies = kwargs.pop("max_request_body_size", None) == "always" # type: bool
|
|
132
|
+
max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int]
|
|
133
|
+
is_vars = kwargs.pop("is_vars", False)
|
|
134
|
+
custom_repr = kwargs.pop("custom_repr", None) # type: Callable[..., Optional[str]]
|
|
135
|
+
|
|
136
|
+
def _safe_repr_wrapper(value):
|
|
137
|
+
# type: (Any) -> str
|
|
138
|
+
try:
|
|
139
|
+
repr_value = None
|
|
140
|
+
if custom_repr is not None:
|
|
141
|
+
repr_value = custom_repr(value)
|
|
142
|
+
return repr_value or safe_repr(value)
|
|
143
|
+
except Exception:
|
|
144
|
+
return safe_repr(value)
|
|
122
145
|
|
|
123
146
|
def _annotate(**meta):
|
|
124
147
|
# type: (**Any) -> None
|
|
125
148
|
while len(meta_stack) <= len(path):
|
|
126
149
|
try:
|
|
127
150
|
segment = path[len(meta_stack) - 1]
|
|
128
|
-
node = meta_stack[-1].setdefault(
|
|
151
|
+
node = meta_stack[-1].setdefault(str(segment), {})
|
|
129
152
|
except IndexError:
|
|
130
153
|
node = {}
|
|
131
154
|
|
|
@@ -133,68 +156,50 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
133
156
|
|
|
134
157
|
meta_stack[-1].setdefault("", {}).update(meta)
|
|
135
158
|
|
|
136
|
-
def
|
|
159
|
+
def _is_databag():
|
|
137
160
|
# type: () -> Optional[bool]
|
|
138
161
|
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
see the difference between a unicode-string and a bytestring
|
|
144
|
-
when viewing a stacktrace.
|
|
145
|
-
|
|
146
|
-
For container-types we still don't do anything different.
|
|
147
|
-
Generally we just try to make the Sentry UI present exactly
|
|
148
|
-
what a pretty-printed repr would look like.
|
|
149
|
-
|
|
150
|
-
:returns: `True` if we are somewhere in frame variables, and `False` if
|
|
151
|
-
we are in a position where we will never encounter frame variables
|
|
152
|
-
when recursing (for example, we're in `event.extra`). `None` if we
|
|
153
|
-
are not (yet) in frame variables, but might encounter them when
|
|
154
|
-
recursing (e.g. we're in `event.exception`)
|
|
162
|
+
A databag is any value that we need to trim.
|
|
163
|
+
True for stuff like vars, request bodies, breadcrumbs and extra.
|
|
164
|
+
|
|
165
|
+
:returns: `True` for "yes", `False` for :"no", `None` for "maybe soon".
|
|
155
166
|
"""
|
|
156
167
|
try:
|
|
168
|
+
if is_vars:
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
is_request_body = _is_request_body()
|
|
172
|
+
if is_request_body in (True, None):
|
|
173
|
+
return is_request_body
|
|
174
|
+
|
|
157
175
|
p0 = path[0]
|
|
158
|
-
if p0 == "
|
|
176
|
+
if p0 == "breadcrumbs" and path[1] == "values":
|
|
177
|
+
path[2]
|
|
159
178
|
return True
|
|
160
179
|
|
|
161
|
-
if
|
|
162
|
-
p0 in ("threads", "exception")
|
|
163
|
-
and path[1] == "values"
|
|
164
|
-
and path[3] == "stacktrace"
|
|
165
|
-
and path[4] == "frames"
|
|
166
|
-
and path[6] == "vars"
|
|
167
|
-
):
|
|
180
|
+
if p0 == "extra":
|
|
168
181
|
return True
|
|
182
|
+
|
|
169
183
|
except IndexError:
|
|
170
184
|
return None
|
|
171
185
|
|
|
172
186
|
return False
|
|
173
187
|
|
|
174
|
-
def
|
|
188
|
+
def _is_span_attribute():
|
|
175
189
|
# type: () -> Optional[bool]
|
|
176
|
-
"""
|
|
177
|
-
A databag is any value that we need to trim.
|
|
178
|
-
|
|
179
|
-
:returns: Works like `_should_repr_strings()`. `True` for "yes",
|
|
180
|
-
`False` for :"no", `None` for "maybe soon".
|
|
181
|
-
"""
|
|
182
190
|
try:
|
|
183
|
-
|
|
184
|
-
if rv in (True, None):
|
|
185
|
-
return rv
|
|
186
|
-
|
|
187
|
-
p0 = path[0]
|
|
188
|
-
if p0 == "request" and path[1] == "data":
|
|
191
|
+
if path[0] == "spans" and path[2] == "data":
|
|
189
192
|
return True
|
|
193
|
+
except IndexError:
|
|
194
|
+
return None
|
|
190
195
|
|
|
191
|
-
|
|
192
|
-
path[1]
|
|
193
|
-
return True
|
|
196
|
+
return False
|
|
194
197
|
|
|
195
|
-
|
|
198
|
+
def _is_request_body():
|
|
199
|
+
# type: () -> Optional[bool]
|
|
200
|
+
try:
|
|
201
|
+
if path[0] == "request" and path[1] == "data":
|
|
196
202
|
return True
|
|
197
|
-
|
|
198
203
|
except IndexError:
|
|
199
204
|
return None
|
|
200
205
|
|
|
@@ -203,10 +208,11 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
203
208
|
def _serialize_node(
|
|
204
209
|
obj, # type: Any
|
|
205
210
|
is_databag=None, # type: Optional[bool]
|
|
211
|
+
is_request_body=None, # type: Optional[bool]
|
|
206
212
|
should_repr_strings=None, # type: Optional[bool]
|
|
207
213
|
segment=None, # type: Optional[Segment]
|
|
208
|
-
remaining_breadth=None, # type: Optional[int]
|
|
209
|
-
remaining_depth=None, # type: Optional[int]
|
|
214
|
+
remaining_breadth=None, # type: Optional[Union[int, float]]
|
|
215
|
+
remaining_depth=None, # type: Optional[Union[int, float]]
|
|
210
216
|
):
|
|
211
217
|
# type: (...) -> Any
|
|
212
218
|
if segment is not None:
|
|
@@ -220,6 +226,7 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
220
226
|
return _serialize_node_impl(
|
|
221
227
|
obj,
|
|
222
228
|
is_databag=is_databag,
|
|
229
|
+
is_request_body=is_request_body,
|
|
223
230
|
should_repr_strings=should_repr_strings,
|
|
224
231
|
remaining_depth=remaining_depth,
|
|
225
232
|
remaining_breadth=remaining_breadth,
|
|
@@ -228,7 +235,7 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
228
235
|
capture_internal_exception(sys.exc_info())
|
|
229
236
|
|
|
230
237
|
if is_databag:
|
|
231
|
-
return
|
|
238
|
+
return "<failed to serialize, use init(debug=True) to see error logs>"
|
|
232
239
|
|
|
233
240
|
return None
|
|
234
241
|
finally:
|
|
@@ -244,72 +251,96 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
244
251
|
return obj
|
|
245
252
|
|
|
246
253
|
def _serialize_node_impl(
|
|
247
|
-
obj,
|
|
254
|
+
obj,
|
|
255
|
+
is_databag,
|
|
256
|
+
is_request_body,
|
|
257
|
+
should_repr_strings,
|
|
258
|
+
remaining_depth,
|
|
259
|
+
remaining_breadth,
|
|
248
260
|
):
|
|
249
|
-
# type: (Any, Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
|
|
261
|
+
# type: (Any, Optional[bool], Optional[bool], Optional[bool], Optional[Union[float, int]], Optional[Union[float, int]]) -> Any
|
|
262
|
+
if isinstance(obj, AnnotatedValue):
|
|
263
|
+
should_repr_strings = False
|
|
250
264
|
if should_repr_strings is None:
|
|
251
|
-
should_repr_strings =
|
|
265
|
+
should_repr_strings = is_vars
|
|
252
266
|
|
|
253
267
|
if is_databag is None:
|
|
254
268
|
is_databag = _is_databag()
|
|
255
269
|
|
|
256
|
-
if
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
270
|
+
if is_request_body is None:
|
|
271
|
+
is_request_body = _is_request_body()
|
|
272
|
+
|
|
273
|
+
if is_databag:
|
|
274
|
+
if is_request_body and keep_request_bodies:
|
|
275
|
+
remaining_depth = float("inf")
|
|
276
|
+
remaining_breadth = float("inf")
|
|
277
|
+
else:
|
|
278
|
+
if remaining_depth is None:
|
|
279
|
+
remaining_depth = MAX_DATABAG_DEPTH
|
|
280
|
+
if remaining_breadth is None:
|
|
281
|
+
remaining_breadth = MAX_DATABAG_BREADTH
|
|
260
282
|
|
|
261
283
|
obj = _flatten_annotated(obj)
|
|
262
284
|
|
|
263
285
|
if remaining_depth is not None and remaining_depth <= 0:
|
|
264
286
|
_annotate(rem=[["!limit", "x"]])
|
|
265
287
|
if is_databag:
|
|
266
|
-
return _flatten_annotated(
|
|
288
|
+
return _flatten_annotated(
|
|
289
|
+
strip_string(_safe_repr_wrapper(obj), max_length=max_value_length)
|
|
290
|
+
)
|
|
267
291
|
return None
|
|
268
292
|
|
|
269
|
-
|
|
293
|
+
is_span_attribute = _is_span_attribute()
|
|
294
|
+
if (is_databag or is_span_attribute) and global_repr_processors:
|
|
270
295
|
hints = {"memo": memo, "remaining_depth": remaining_depth}
|
|
271
296
|
for processor in global_repr_processors:
|
|
272
297
|
result = processor(obj, hints)
|
|
273
298
|
if result is not NotImplemented:
|
|
274
299
|
return _flatten_annotated(result)
|
|
275
300
|
|
|
276
|
-
|
|
301
|
+
sentry_repr = getattr(type(obj), "__sentry_repr__", None)
|
|
302
|
+
|
|
303
|
+
if obj is None or isinstance(obj, (bool, int, float)):
|
|
277
304
|
if should_repr_strings or (
|
|
278
305
|
isinstance(obj, float) and (math.isinf(obj) or math.isnan(obj))
|
|
279
306
|
):
|
|
280
|
-
return
|
|
307
|
+
return _safe_repr_wrapper(obj)
|
|
281
308
|
else:
|
|
282
309
|
return obj
|
|
283
310
|
|
|
311
|
+
elif callable(sentry_repr):
|
|
312
|
+
return sentry_repr(obj)
|
|
313
|
+
|
|
284
314
|
elif isinstance(obj, datetime):
|
|
285
315
|
return (
|
|
286
|
-
|
|
316
|
+
str(format_timestamp(obj))
|
|
287
317
|
if not should_repr_strings
|
|
288
|
-
else
|
|
318
|
+
else _safe_repr_wrapper(obj)
|
|
289
319
|
)
|
|
290
320
|
|
|
291
321
|
elif isinstance(obj, Mapping):
|
|
292
322
|
# Create temporary copy here to avoid calling too much code that
|
|
293
323
|
# might mutate our dictionary while we're still iterating over it.
|
|
294
|
-
obj = dict(
|
|
324
|
+
obj = dict(obj.items())
|
|
295
325
|
|
|
296
326
|
rv_dict = {} # type: Dict[str, Any]
|
|
297
327
|
i = 0
|
|
298
328
|
|
|
299
|
-
for k, v in
|
|
329
|
+
for k, v in obj.items():
|
|
300
330
|
if remaining_breadth is not None and i >= remaining_breadth:
|
|
301
331
|
_annotate(len=len(obj))
|
|
302
332
|
break
|
|
303
333
|
|
|
304
|
-
str_k =
|
|
334
|
+
str_k = str(k)
|
|
305
335
|
v = _serialize_node(
|
|
306
336
|
v,
|
|
307
337
|
segment=str_k,
|
|
308
338
|
should_repr_strings=should_repr_strings,
|
|
309
339
|
is_databag=is_databag,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
340
|
+
is_request_body=is_request_body,
|
|
341
|
+
remaining_depth=(
|
|
342
|
+
remaining_depth - 1 if remaining_depth is not None else None
|
|
343
|
+
),
|
|
313
344
|
remaining_breadth=remaining_breadth,
|
|
314
345
|
)
|
|
315
346
|
rv_dict[str_k] = v
|
|
@@ -318,7 +349,7 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
318
349
|
return rv_dict
|
|
319
350
|
|
|
320
351
|
elif not isinstance(obj, serializable_str_types) and isinstance(
|
|
321
|
-
obj, (
|
|
352
|
+
obj, tuple(sequence_types)
|
|
322
353
|
):
|
|
323
354
|
rv_list = []
|
|
324
355
|
|
|
@@ -333,9 +364,10 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
333
364
|
segment=i,
|
|
334
365
|
should_repr_strings=should_repr_strings,
|
|
335
366
|
is_databag=is_databag,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
367
|
+
is_request_body=is_request_body,
|
|
368
|
+
remaining_depth=(
|
|
369
|
+
remaining_depth - 1 if remaining_depth is not None else None
|
|
370
|
+
),
|
|
339
371
|
remaining_breadth=remaining_breadth,
|
|
340
372
|
)
|
|
341
373
|
)
|
|
@@ -343,121 +375,31 @@ def serialize(event, smart_transaction_trimming=False, **kwargs):
|
|
|
343
375
|
return rv_list
|
|
344
376
|
|
|
345
377
|
if should_repr_strings:
|
|
346
|
-
obj =
|
|
378
|
+
obj = _safe_repr_wrapper(obj)
|
|
347
379
|
else:
|
|
348
|
-
if isinstance(obj, bytes):
|
|
380
|
+
if isinstance(obj, bytes) or isinstance(obj, bytearray):
|
|
349
381
|
obj = obj.decode("utf-8", "replace")
|
|
350
382
|
|
|
351
|
-
if not isinstance(obj,
|
|
352
|
-
obj =
|
|
353
|
-
|
|
354
|
-
# Allow span descriptions to be longer than other strings.
|
|
355
|
-
#
|
|
356
|
-
# For database auto-instrumented spans, the description contains
|
|
357
|
-
# potentially long SQL queries that are most useful when not truncated.
|
|
358
|
-
# Because arbitrarily large events may be discarded by the server as a
|
|
359
|
-
# protection mechanism, we dynamically limit the description length
|
|
360
|
-
# later in _truncate_span_descriptions.
|
|
361
|
-
if (
|
|
362
|
-
smart_transaction_trimming
|
|
363
|
-
and len(path) == 3
|
|
364
|
-
and path[0] == "spans"
|
|
365
|
-
and path[-1] == "description"
|
|
366
|
-
):
|
|
367
|
-
span_description_bytes.append(len(obj))
|
|
368
|
-
return obj
|
|
369
|
-
return _flatten_annotated(strip_string(obj))
|
|
383
|
+
if not isinstance(obj, str):
|
|
384
|
+
obj = _safe_repr_wrapper(obj)
|
|
370
385
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
span timestamps (represented as RFC3399-formatted strings in
|
|
377
|
-
serialized_event).
|
|
378
|
-
|
|
379
|
-
It uses heuristics to prioritize preserving the description of spans
|
|
380
|
-
that might be the most interesting ones in terms of understanding and
|
|
381
|
-
optimizing performance.
|
|
382
|
-
"""
|
|
383
|
-
# When truncating a description, preserve a small prefix.
|
|
384
|
-
min_length = 10
|
|
385
|
-
|
|
386
|
-
def shortest_duration_longest_description_first(args):
|
|
387
|
-
# type: (Tuple[int, Span]) -> Tuple[timedelta, int]
|
|
388
|
-
i, serialized_span = args
|
|
389
|
-
span = event["spans"][i]
|
|
390
|
-
now = datetime.utcnow()
|
|
391
|
-
start = span.get("start_timestamp") or now
|
|
392
|
-
end = span.get("timestamp") or now
|
|
393
|
-
duration = end - start
|
|
394
|
-
description = serialized_span.get("description") or ""
|
|
395
|
-
return (duration, -len(description))
|
|
396
|
-
|
|
397
|
-
# Note: for simplicity we sort spans by exact duration and description
|
|
398
|
-
# length. If ever needed, we could have a more involved heuristic, e.g.
|
|
399
|
-
# replacing exact durations with "buckets" and/or looking at other span
|
|
400
|
-
# properties.
|
|
401
|
-
path.append("spans")
|
|
402
|
-
for i, span in sorted(
|
|
403
|
-
enumerate(serialized_event.get("spans") or []),
|
|
404
|
-
key=shortest_duration_longest_description_first,
|
|
405
|
-
):
|
|
406
|
-
description = span.get("description") or ""
|
|
407
|
-
if len(description) <= min_length:
|
|
408
|
-
continue
|
|
409
|
-
excess_bytes -= len(description) - min_length
|
|
410
|
-
path.extend([i, "description"])
|
|
411
|
-
# Note: the last time we call strip_string we could preserve a few
|
|
412
|
-
# more bytes up to a total length of MAX_EVENT_BYTES. Since that's
|
|
413
|
-
# not strictly required, we leave it out for now for simplicity.
|
|
414
|
-
span["description"] = _flatten_annotated(
|
|
415
|
-
strip_string(description, max_length=min_length)
|
|
416
|
-
)
|
|
417
|
-
del path[-2:]
|
|
418
|
-
del meta_stack[len(path) + 1 :]
|
|
386
|
+
is_span_description = (
|
|
387
|
+
len(path) == 3 and path[0] == "spans" and path[-1] == "description"
|
|
388
|
+
)
|
|
389
|
+
if is_span_description:
|
|
390
|
+
return obj
|
|
419
391
|
|
|
420
|
-
|
|
421
|
-
break
|
|
422
|
-
path.pop()
|
|
423
|
-
del meta_stack[len(path) + 1 :]
|
|
392
|
+
return _flatten_annotated(strip_string(obj, max_length=max_value_length))
|
|
424
393
|
|
|
394
|
+
#
|
|
395
|
+
# Start of serialize() function
|
|
396
|
+
#
|
|
425
397
|
disable_capture_event.set(True)
|
|
426
398
|
try:
|
|
427
|
-
|
|
428
|
-
if meta_stack and isinstance(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if smart_transaction_trimming and sum_span_description_bytes > 0:
|
|
433
|
-
span_count = len(event.get("spans") or [])
|
|
434
|
-
# This is an upper bound of how many bytes all descriptions would
|
|
435
|
-
# consume if the usual string truncation in _serialize_node_impl
|
|
436
|
-
# would have taken place, not accounting for the metadata attached
|
|
437
|
-
# as event["_meta"].
|
|
438
|
-
descriptions_budget_bytes = span_count * sentry_sdk.utils.MAX_STRING_LENGTH
|
|
439
|
-
|
|
440
|
-
# If by not truncating descriptions we ended up with more bytes than
|
|
441
|
-
# per the usual string truncation, check if the event is too large
|
|
442
|
-
# and we need to truncate some descriptions.
|
|
443
|
-
#
|
|
444
|
-
# This is guarded with an if statement to avoid JSON-encoding the
|
|
445
|
-
# event unnecessarily.
|
|
446
|
-
if sum_span_description_bytes > descriptions_budget_bytes:
|
|
447
|
-
original_bytes = len(json_dumps(rv))
|
|
448
|
-
excess_bytes = original_bytes - MAX_EVENT_BYTES
|
|
449
|
-
if excess_bytes > 0:
|
|
450
|
-
# Event is too large, will likely be discarded by the
|
|
451
|
-
# server. Trim it down before sending.
|
|
452
|
-
_truncate_span_descriptions(rv, event, excess_bytes)
|
|
453
|
-
|
|
454
|
-
# Span descriptions truncated, set or reset _meta.
|
|
455
|
-
#
|
|
456
|
-
# We run the same code earlier because we want to account
|
|
457
|
-
# for _meta when calculating original_bytes, the number of
|
|
458
|
-
# bytes in the JSON-encoded event.
|
|
459
|
-
if meta_stack and isinstance(rv, dict):
|
|
460
|
-
rv["_meta"] = meta_stack[0]
|
|
461
|
-
return rv
|
|
399
|
+
serialized_event = _serialize_node(event, **kwargs)
|
|
400
|
+
if not is_vars and meta_stack and isinstance(serialized_event, dict):
|
|
401
|
+
serialized_event["_meta"] = meta_stack[0]
|
|
402
|
+
|
|
403
|
+
return serialized_event
|
|
462
404
|
finally:
|
|
463
405
|
disable_capture_event.set(False)
|