sentry-sdk 0.7.5__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 -30
  2. sentry_sdk/_compat.py +74 -61
  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 +289 -0
  8. sentry_sdk/_types.py +338 -0
  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 +496 -80
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +1023 -103
  16. sentry_sdk/consts.py +1438 -66
  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 +15 -14
  22. sentry_sdk/envelope.py +369 -0
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +611 -280
  25. sentry_sdk/integrations/__init__.py +276 -49
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +180 -44
  28. sentry_sdk/integrations/aiohttp.py +291 -42
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +9 -8
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +341 -0
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +17 -10
  37. sentry_sdk/integrations/aws_lambda.py +377 -62
  38. sentry_sdk/integrations/beam.py +176 -0
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +221 -0
  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 +134 -0
  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 +48 -14
  49. sentry_sdk/integrations/django/__init__.py +584 -191
  50. sentry_sdk/integrations/django/asgi.py +245 -0
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +187 -0
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +79 -5
  55. sentry_sdk/integrations/django/transactions.py +49 -22
  56. sentry_sdk/integrations/django/views.py +96 -0
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +50 -13
  59. sentry_sdk/integrations/executing.py +67 -0
  60. sentry_sdk/integrations/falcon.py +272 -0
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +142 -88
  63. sentry_sdk/integrations/gcp.py +239 -0
  64. sentry_sdk/integrations/gnu_backtrace.py +99 -0
  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 +307 -96
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +14 -31
  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 +141 -0
  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 +112 -68
  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 +95 -37
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +294 -123
  145. sentry_sdk/integrations/serverless.py +48 -19
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/__init__.py +4 -0
  148. sentry_sdk/integrations/spark/spark_driver.py +316 -0
  149. sentry_sdk/integrations/spark/spark_worker.py +116 -0
  150. sentry_sdk/integrations/sqlalchemy.py +142 -0
  151. sentry_sdk/integrations/starlette.py +737 -0
  152. sentry_sdk/integrations/starlite.py +292 -0
  153. sentry_sdk/integrations/statsig.py +37 -0
  154. sentry_sdk/integrations/stdlib.py +235 -29
  155. sentry_sdk/integrations/strawberry.py +394 -0
  156. sentry_sdk/integrations/sys_exit.py +70 -0
  157. sentry_sdk/integrations/threading.py +158 -28
  158. sentry_sdk/integrations/tornado.py +84 -52
  159. sentry_sdk/integrations/trytond.py +50 -0
  160. sentry_sdk/integrations/typer.py +60 -0
  161. sentry_sdk/integrations/unleash.py +33 -0
  162. sentry_sdk/integrations/unraisablehook.py +53 -0
  163. sentry_sdk/integrations/wsgi.py +201 -119
  164. sentry_sdk/logger.py +96 -0
  165. sentry_sdk/metrics.py +81 -0
  166. sentry_sdk/monitor.py +120 -0
  167. sentry_sdk/profiler/__init__.py +49 -0
  168. sentry_sdk/profiler/continuous_profiler.py +730 -0
  169. sentry_sdk/profiler/transaction_profiler.py +839 -0
  170. sentry_sdk/profiler/utils.py +195 -0
  171. sentry_sdk/py.typed +0 -0
  172. sentry_sdk/scope.py +1713 -85
  173. sentry_sdk/scrubber.py +177 -0
  174. sentry_sdk/serializer.py +405 -0
  175. sentry_sdk/session.py +177 -0
  176. sentry_sdk/sessions.py +275 -0
  177. sentry_sdk/spotlight.py +242 -0
  178. sentry_sdk/tracing.py +1486 -0
  179. sentry_sdk/tracing_utils.py +1236 -0
  180. sentry_sdk/transport.py +806 -134
  181. sentry_sdk/types.py +52 -0
  182. sentry_sdk/utils.py +1625 -465
  183. sentry_sdk/worker.py +54 -25
  184. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  185. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  186. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  187. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  188. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  189. sentry_sdk/integrations/celery.py +0 -119
  190. sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.7.5.dist-info/METADATA +0 -36
  192. sentry_sdk-0.7.5.dist-info/RECORD +0 -39
  193. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
sentry_sdk/scrubber.py ADDED
@@ -0,0 +1,177 @@
1
+ from sentry_sdk.utils import (
2
+ capture_internal_exceptions,
3
+ AnnotatedValue,
4
+ iter_event_frames,
5
+ )
6
+
7
+ from typing import TYPE_CHECKING, cast, List, Dict
8
+
9
+ if TYPE_CHECKING:
10
+ from sentry_sdk._types import Event
11
+ from typing import Optional
12
+
13
+
14
+ DEFAULT_DENYLIST = [
15
+ # stolen from relay
16
+ "password",
17
+ "passwd",
18
+ "secret",
19
+ "api_key",
20
+ "apikey",
21
+ "auth",
22
+ "credentials",
23
+ "mysql_pwd",
24
+ "privatekey",
25
+ "private_key",
26
+ "token",
27
+ "session",
28
+ # django
29
+ "csrftoken",
30
+ "sessionid",
31
+ # wsgi
32
+ "x_csrftoken",
33
+ "x_forwarded_for",
34
+ "set_cookie",
35
+ "cookie",
36
+ "authorization",
37
+ "x_api_key",
38
+ # other common names used in the wild
39
+ "aiohttp_session", # aiohttp
40
+ "connect.sid", # Express
41
+ "csrf_token", # Pyramid
42
+ "csrf", # (this is a cookie name used in accepted answers on stack overflow)
43
+ "_csrf", # Express
44
+ "_csrf_token", # Bottle
45
+ "PHPSESSID", # PHP
46
+ "_session", # Sanic
47
+ "symfony", # Symfony
48
+ "user_session", # Vue
49
+ "_xsrf", # Tornado
50
+ "XSRF-TOKEN", # Angular, Laravel
51
+ ]
52
+
53
+ DEFAULT_PII_DENYLIST = [
54
+ "x_forwarded_for",
55
+ "x_real_ip",
56
+ "ip_address",
57
+ "remote_addr",
58
+ ]
59
+
60
+
61
+ class EventScrubber:
62
+ def __init__(
63
+ self, denylist=None, recursive=False, send_default_pii=False, pii_denylist=None
64
+ ):
65
+ # type: (Optional[List[str]], bool, bool, Optional[List[str]]) -> None
66
+ """
67
+ A scrubber that goes through the event payload and removes sensitive data configured through denylists.
68
+
69
+ :param denylist: A security denylist that is always scrubbed, defaults to DEFAULT_DENYLIST.
70
+ :param recursive: Whether to scrub the event payload recursively, default False.
71
+ :param send_default_pii: Whether pii is sending is on, pii fields are not scrubbed.
72
+ :param pii_denylist: The denylist to use for scrubbing when pii is not sent, defaults to DEFAULT_PII_DENYLIST.
73
+ """
74
+ self.denylist = DEFAULT_DENYLIST.copy() if denylist is None else denylist
75
+
76
+ if not send_default_pii:
77
+ pii_denylist = (
78
+ DEFAULT_PII_DENYLIST.copy() if pii_denylist is None else pii_denylist
79
+ )
80
+ self.denylist += pii_denylist
81
+
82
+ self.denylist = [x.lower() for x in self.denylist]
83
+ self.recursive = recursive
84
+
85
+ def scrub_list(self, lst):
86
+ # type: (object) -> None
87
+ """
88
+ If a list is passed to this method, the method recursively searches the list and any
89
+ nested lists for any dictionaries. The method calls scrub_dict on all dictionaries
90
+ it finds.
91
+ If the parameter passed to this method is not a list, the method does nothing.
92
+ """
93
+ if not isinstance(lst, list):
94
+ return
95
+
96
+ for v in lst:
97
+ self.scrub_dict(v) # no-op unless v is a dict
98
+ self.scrub_list(v) # no-op unless v is a list
99
+
100
+ def scrub_dict(self, d):
101
+ # type: (object) -> None
102
+ """
103
+ If a dictionary is passed to this method, the method scrubs the dictionary of any
104
+ sensitive data. The method calls itself recursively on any nested dictionaries (
105
+ including dictionaries nested in lists) if self.recursive is True.
106
+ This method does nothing if the parameter passed to it is not a dictionary.
107
+ """
108
+ if not isinstance(d, dict):
109
+ return
110
+
111
+ for k, v in d.items():
112
+ # The cast is needed because mypy is not smart enough to figure out that k must be a
113
+ # string after the isinstance check.
114
+ if isinstance(k, str) and k.lower() in self.denylist:
115
+ d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
116
+ elif self.recursive:
117
+ self.scrub_dict(v) # no-op unless v is a dict
118
+ self.scrub_list(v) # no-op unless v is a list
119
+
120
+ def scrub_request(self, event):
121
+ # type: (Event) -> None
122
+ with capture_internal_exceptions():
123
+ if "request" in event:
124
+ if "headers" in event["request"]:
125
+ self.scrub_dict(event["request"]["headers"])
126
+ if "cookies" in event["request"]:
127
+ self.scrub_dict(event["request"]["cookies"])
128
+ if "data" in event["request"]:
129
+ self.scrub_dict(event["request"]["data"])
130
+
131
+ def scrub_extra(self, event):
132
+ # type: (Event) -> None
133
+ with capture_internal_exceptions():
134
+ if "extra" in event:
135
+ self.scrub_dict(event["extra"])
136
+
137
+ def scrub_user(self, event):
138
+ # type: (Event) -> None
139
+ with capture_internal_exceptions():
140
+ if "user" in event:
141
+ self.scrub_dict(event["user"])
142
+
143
+ def scrub_breadcrumbs(self, event):
144
+ # type: (Event) -> None
145
+ with capture_internal_exceptions():
146
+ if "breadcrumbs" in event:
147
+ if (
148
+ not isinstance(event["breadcrumbs"], AnnotatedValue)
149
+ and "values" in event["breadcrumbs"]
150
+ ):
151
+ for value in event["breadcrumbs"]["values"]:
152
+ if "data" in value:
153
+ self.scrub_dict(value["data"])
154
+
155
+ def scrub_frames(self, event):
156
+ # type: (Event) -> None
157
+ with capture_internal_exceptions():
158
+ for frame in iter_event_frames(event):
159
+ if "vars" in frame:
160
+ self.scrub_dict(frame["vars"])
161
+
162
+ def scrub_spans(self, event):
163
+ # type: (Event) -> None
164
+ with capture_internal_exceptions():
165
+ if "spans" in event:
166
+ for span in cast(List[Dict[str, object]], event["spans"]):
167
+ if "data" in span:
168
+ self.scrub_dict(span["data"])
169
+
170
+ def scrub_event(self, event):
171
+ # type: (Event) -> None
172
+ self.scrub_request(event)
173
+ self.scrub_extra(event)
174
+ self.scrub_user(event)
175
+ self.scrub_breadcrumbs(event)
176
+ self.scrub_frames(event)
177
+ self.scrub_spans(event)
@@ -0,0 +1,405 @@
1
+ import sys
2
+ import math
3
+ from collections.abc import Mapping, Sequence, Set
4
+ from datetime import datetime
5
+
6
+ from sentry_sdk.utils import (
7
+ AnnotatedValue,
8
+ capture_internal_exception,
9
+ disable_capture_event,
10
+ format_timestamp,
11
+ safe_repr,
12
+ strip_string,
13
+ )
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from types import TracebackType
19
+
20
+ from typing import Any
21
+ from typing import Callable
22
+ from typing import ContextManager
23
+ from typing import Dict
24
+ from typing import List
25
+ from typing import Optional
26
+ from typing import Type
27
+ from typing import Union
28
+
29
+ from sentry_sdk._types import NotImplementedType
30
+
31
+ Span = Dict[str, Any]
32
+
33
+ ReprProcessor = Callable[[Any, Dict[str, Any]], Union[NotImplementedType, str]]
34
+ Segment = Union[str, int]
35
+
36
+
37
+ # Bytes are technically not strings in Python 3, but we can serialize them
38
+ serializable_str_types = (str, bytes, bytearray, memoryview)
39
+
40
+
41
+ # Maximum length of JSON-serialized event payloads that can be safely sent
42
+ # before the server may reject the event due to its size. This is not intended
43
+ # to reflect actual values defined server-side, but rather only be an upper
44
+ # bound for events sent by the SDK.
45
+ #
46
+ # Can be overwritten if wanting to send more bytes, e.g. with a custom server.
47
+ # When changing this, keep in mind that events may be a little bit larger than
48
+ # this value due to attached metadata, so keep the number conservative.
49
+ MAX_EVENT_BYTES = 10**6
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.
53
+ MAX_DATABAG_DEPTH = 5
54
+ MAX_DATABAG_BREADTH = 10
55
+ CYCLE_MARKER = "<cyclic>"
56
+
57
+
58
+ global_repr_processors = [] # type: List[ReprProcessor]
59
+
60
+
61
+ def add_global_repr_processor(processor):
62
+ # type: (ReprProcessor) -> None
63
+ global_repr_processors.append(processor)
64
+
65
+
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:
75
+ __slots__ = ("_ids", "_objs")
76
+
77
+ def __init__(self):
78
+ # type: () -> None
79
+ self._ids = {} # type: Dict[int, Any]
80
+ self._objs = [] # type: List[Any]
81
+
82
+ def memoize(self, obj):
83
+ # type: (Any) -> ContextManager[bool]
84
+ self._objs.append(obj)
85
+ return self
86
+
87
+ def __enter__(self):
88
+ # type: () -> bool
89
+ obj = self._objs[-1]
90
+ if id(obj) in self._ids:
91
+ return True
92
+ else:
93
+ self._ids[id(obj)] = obj
94
+ return False
95
+
96
+ def __exit__(
97
+ self,
98
+ ty, # type: Optional[Type[BaseException]]
99
+ value, # type: Optional[BaseException]
100
+ tb, # type: Optional[TracebackType]
101
+ ):
102
+ # type: (...) -> None
103
+ self._ids.pop(id(self._objs.pop()), None)
104
+
105
+
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
+ """
127
+ memo = Memo()
128
+ path = [] # type: List[Segment]
129
+ meta_stack = [] # type: List[Dict[str, Any]]
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)
145
+
146
+ def _annotate(**meta):
147
+ # type: (**Any) -> None
148
+ while len(meta_stack) <= len(path):
149
+ try:
150
+ segment = path[len(meta_stack) - 1]
151
+ node = meta_stack[-1].setdefault(str(segment), {})
152
+ except IndexError:
153
+ node = {}
154
+
155
+ meta_stack.append(node)
156
+
157
+ meta_stack[-1].setdefault("", {}).update(meta)
158
+
159
+ def _is_databag():
160
+ # type: () -> Optional[bool]
161
+ """
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".
166
+ """
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
+
175
+ p0 = path[0]
176
+ if p0 == "breadcrumbs" and path[1] == "values":
177
+ path[2]
178
+ return True
179
+
180
+ if p0 == "extra":
181
+ return True
182
+
183
+ except IndexError:
184
+ return None
185
+
186
+ return False
187
+
188
+ def _is_span_attribute():
189
+ # type: () -> Optional[bool]
190
+ try:
191
+ if path[0] == "spans" and path[2] == "data":
192
+ return True
193
+ except IndexError:
194
+ return None
195
+
196
+ return False
197
+
198
+ def _is_request_body():
199
+ # type: () -> Optional[bool]
200
+ try:
201
+ if path[0] == "request" and path[1] == "data":
202
+ return True
203
+ except IndexError:
204
+ return None
205
+
206
+ return False
207
+
208
+ def _serialize_node(
209
+ obj, # type: Any
210
+ is_databag=None, # type: Optional[bool]
211
+ is_request_body=None, # type: Optional[bool]
212
+ should_repr_strings=None, # type: Optional[bool]
213
+ segment=None, # type: Optional[Segment]
214
+ remaining_breadth=None, # type: Optional[Union[int, float]]
215
+ remaining_depth=None, # type: Optional[Union[int, float]]
216
+ ):
217
+ # type: (...) -> Any
218
+ if segment is not None:
219
+ path.append(segment)
220
+
221
+ try:
222
+ with memo.memoize(obj) as result:
223
+ if result:
224
+ return CYCLE_MARKER
225
+
226
+ return _serialize_node_impl(
227
+ obj,
228
+ is_databag=is_databag,
229
+ is_request_body=is_request_body,
230
+ should_repr_strings=should_repr_strings,
231
+ remaining_depth=remaining_depth,
232
+ remaining_breadth=remaining_breadth,
233
+ )
234
+ except BaseException:
235
+ capture_internal_exception(sys.exc_info())
236
+
237
+ if is_databag:
238
+ return "<failed to serialize, use init(debug=True) to see error logs>"
239
+
240
+ return None
241
+ finally:
242
+ if segment is not None:
243
+ path.pop()
244
+ del meta_stack[len(path) + 1 :]
245
+
246
+ def _flatten_annotated(obj):
247
+ # type: (Any) -> Any
248
+ if isinstance(obj, AnnotatedValue):
249
+ _annotate(**obj.metadata)
250
+ obj = obj.value
251
+ return obj
252
+
253
+ def _serialize_node_impl(
254
+ obj,
255
+ is_databag,
256
+ is_request_body,
257
+ should_repr_strings,
258
+ remaining_depth,
259
+ remaining_breadth,
260
+ ):
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
264
+ if should_repr_strings is None:
265
+ should_repr_strings = is_vars
266
+
267
+ if is_databag is None:
268
+ is_databag = _is_databag()
269
+
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
282
+
283
+ obj = _flatten_annotated(obj)
284
+
285
+ if remaining_depth is not None and remaining_depth <= 0:
286
+ _annotate(rem=[["!limit", "x"]])
287
+ if is_databag:
288
+ return _flatten_annotated(
289
+ strip_string(_safe_repr_wrapper(obj), max_length=max_value_length)
290
+ )
291
+ return None
292
+
293
+ is_span_attribute = _is_span_attribute()
294
+ if (is_databag or is_span_attribute) and global_repr_processors:
295
+ hints = {"memo": memo, "remaining_depth": remaining_depth}
296
+ for processor in global_repr_processors:
297
+ result = processor(obj, hints)
298
+ if result is not NotImplemented:
299
+ return _flatten_annotated(result)
300
+
301
+ sentry_repr = getattr(type(obj), "__sentry_repr__", None)
302
+
303
+ if obj is None or isinstance(obj, (bool, int, float)):
304
+ if should_repr_strings or (
305
+ isinstance(obj, float) and (math.isinf(obj) or math.isnan(obj))
306
+ ):
307
+ return _safe_repr_wrapper(obj)
308
+ else:
309
+ return obj
310
+
311
+ elif callable(sentry_repr):
312
+ return sentry_repr(obj)
313
+
314
+ elif isinstance(obj, datetime):
315
+ return (
316
+ str(format_timestamp(obj))
317
+ if not should_repr_strings
318
+ else _safe_repr_wrapper(obj)
319
+ )
320
+
321
+ elif isinstance(obj, Mapping):
322
+ # Create temporary copy here to avoid calling too much code that
323
+ # might mutate our dictionary while we're still iterating over it.
324
+ obj = dict(obj.items())
325
+
326
+ rv_dict = {} # type: Dict[str, Any]
327
+ i = 0
328
+
329
+ for k, v in obj.items():
330
+ if remaining_breadth is not None and i >= remaining_breadth:
331
+ _annotate(len=len(obj))
332
+ break
333
+
334
+ str_k = str(k)
335
+ v = _serialize_node(
336
+ v,
337
+ segment=str_k,
338
+ should_repr_strings=should_repr_strings,
339
+ is_databag=is_databag,
340
+ is_request_body=is_request_body,
341
+ remaining_depth=(
342
+ remaining_depth - 1 if remaining_depth is not None else None
343
+ ),
344
+ remaining_breadth=remaining_breadth,
345
+ )
346
+ rv_dict[str_k] = v
347
+ i += 1
348
+
349
+ return rv_dict
350
+
351
+ elif not isinstance(obj, serializable_str_types) and isinstance(
352
+ obj, tuple(sequence_types)
353
+ ):
354
+ rv_list = []
355
+
356
+ for i, v in enumerate(obj):
357
+ if remaining_breadth is not None and i >= remaining_breadth:
358
+ _annotate(len=len(obj))
359
+ break
360
+
361
+ rv_list.append(
362
+ _serialize_node(
363
+ v,
364
+ segment=i,
365
+ should_repr_strings=should_repr_strings,
366
+ is_databag=is_databag,
367
+ is_request_body=is_request_body,
368
+ remaining_depth=(
369
+ remaining_depth - 1 if remaining_depth is not None else None
370
+ ),
371
+ remaining_breadth=remaining_breadth,
372
+ )
373
+ )
374
+
375
+ return rv_list
376
+
377
+ if should_repr_strings:
378
+ obj = _safe_repr_wrapper(obj)
379
+ else:
380
+ if isinstance(obj, bytes) or isinstance(obj, bytearray):
381
+ obj = obj.decode("utf-8", "replace")
382
+
383
+ if not isinstance(obj, str):
384
+ obj = _safe_repr_wrapper(obj)
385
+
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
391
+
392
+ return _flatten_annotated(strip_string(obj, max_length=max_value_length))
393
+
394
+ #
395
+ # Start of serialize() function
396
+ #
397
+ disable_capture_event.set(True)
398
+ try:
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
404
+ finally:
405
+ disable_capture_event.set(False)