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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {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 sentry_sdk.utils
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, Event
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
- if PY2:
46
- # Importing ABCs from collections is deprecated, and will stop working in 3.8
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 ** 6
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 = u"<cyclic>"
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
- class Memo(object):
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, smart_transaction_trimming=False, **kwargs):
117
- # type: (Event, bool, **Any) -> Event
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
- span_description_bytes = [] # type: List[int]
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(text_type(segment), {})
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 _should_repr_strings():
159
+ def _is_databag():
137
160
  # type: () -> Optional[bool]
138
161
  """
139
- By default non-serializable objects are going through
140
- safe_repr(). For certain places in the event (local vars) we
141
- want to repr() even things that are JSON-serializable to
142
- make their type more apparent. For example, it's useful to
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 == "stacktrace" and path[1] == "frames" and path[3] == "vars":
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 _is_databag():
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
- rv = _should_repr_strings()
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
- if p0 == "breadcrumbs":
192
- path[1]
193
- return True
196
+ return False
194
197
 
195
- if p0 == "extra":
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 u"<failed to serialize, use init(debug=True) to see error logs>"
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, is_databag, should_repr_strings, remaining_depth, remaining_breadth
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 = _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 is_databag and remaining_depth is None:
257
- remaining_depth = MAX_DATABAG_DEPTH
258
- if is_databag and remaining_breadth is None:
259
- remaining_breadth = MAX_DATABAG_BREADTH
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(strip_string(safe_repr(obj)))
288
+ return _flatten_annotated(
289
+ strip_string(_safe_repr_wrapper(obj), max_length=max_value_length)
290
+ )
267
291
  return None
268
292
 
269
- if is_databag and global_repr_processors:
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
- if obj is None or isinstance(obj, (bool, number_types)):
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 safe_repr(obj)
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
- text_type(format_timestamp(obj))
316
+ str(format_timestamp(obj))
287
317
  if not should_repr_strings
288
- else safe_repr(obj)
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(iteritems(obj))
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 iteritems(obj):
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 = text_type(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
- remaining_depth=remaining_depth - 1
311
- if remaining_depth is not None
312
- else None,
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, (Set, Sequence)
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
- remaining_depth=remaining_depth - 1
337
- if remaining_depth is not None
338
- else None,
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 = safe_repr(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, string_types):
352
- obj = safe_repr(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
- def _truncate_span_descriptions(serialized_event, event, excess_bytes):
372
- # type: (Event, Event, int) -> None
373
- """
374
- Modifies serialized_event in-place trying to remove excess_bytes from
375
- span descriptions. The original event is used read-only to access the
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
- if excess_bytes <= 0:
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
- rv = _serialize_node(event, **kwargs)
428
- if meta_stack and isinstance(rv, dict):
429
- rv["_meta"] = meta_stack[0]
430
-
431
- sum_span_description_bytes = sum(span_description_bytes)
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)