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/scope.py CHANGED
@@ -1,46 +1,170 @@
1
- from copy import copy
1
+ import os
2
+ import sys
3
+ import warnings
4
+ from copy import copy, deepcopy
2
5
  from collections import deque
6
+ from contextlib import contextmanager
7
+ from enum import Enum
8
+ from datetime import datetime, timezone
9
+ from functools import wraps
3
10
  from itertools import chain
4
11
 
5
- from sentry_sdk._functools import wraps
6
- from sentry_sdk._types import MYPY
7
- from sentry_sdk.utils import logger, capture_internal_exceptions
8
- from sentry_sdk.tracing import Transaction
12
+ from sentry_sdk._types import AnnotatedValue
13
+ from sentry_sdk.attachments import Attachment
14
+ from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES, INSTRUMENTER
15
+ from sentry_sdk.feature_flags import FlagBuffer, DEFAULT_FLAG_CAPACITY
16
+ from sentry_sdk.profiler.continuous_profiler import (
17
+ get_profiler_id,
18
+ try_autostart_continuous_profiler,
19
+ try_profile_lifecycle_trace_start,
20
+ )
21
+ from sentry_sdk.profiler.transaction_profiler import Profile
22
+ from sentry_sdk.session import Session
23
+ from sentry_sdk.tracing_utils import (
24
+ Baggage,
25
+ has_tracing_enabled,
26
+ normalize_incoming_data,
27
+ PropagationContext,
28
+ )
29
+ from sentry_sdk.tracing import (
30
+ BAGGAGE_HEADER_NAME,
31
+ SENTRY_TRACE_HEADER_NAME,
32
+ NoOpSpan,
33
+ Span,
34
+ Transaction,
35
+ )
36
+ from sentry_sdk.utils import (
37
+ capture_internal_exception,
38
+ capture_internal_exceptions,
39
+ ContextVar,
40
+ datetime_from_isoformat,
41
+ disable_capture_event,
42
+ event_from_exception,
43
+ exc_info_from_error,
44
+ logger,
45
+ )
46
+
47
+ import typing
48
+ from typing import TYPE_CHECKING
49
+
50
+ if TYPE_CHECKING:
51
+ from collections.abc import Mapping
9
52
 
10
- if MYPY:
11
53
  from typing import Any
12
- from typing import Dict
13
- from typing import Optional
54
+ from typing import Callable
14
55
  from typing import Deque
56
+ from typing import Dict
57
+ from typing import Generator
58
+ from typing import Iterator
15
59
  from typing import List
16
- from typing import Callable
60
+ from typing import Optional
61
+ from typing import ParamSpec
62
+ from typing import Tuple
17
63
  from typing import TypeVar
64
+ from typing import Union
65
+
66
+ from typing_extensions import Unpack
18
67
 
19
68
  from sentry_sdk._types import (
20
69
  Breadcrumb,
70
+ BreadcrumbHint,
71
+ ErrorProcessor,
21
72
  Event,
22
73
  EventProcessor,
23
- ErrorProcessor,
24
74
  ExcInfo,
25
75
  Hint,
76
+ LogLevelStr,
77
+ SamplingContext,
26
78
  Type,
27
79
  )
28
80
 
29
- from sentry_sdk.tracing import Span
30
- from sentry_sdk.sessions import Session
81
+ from sentry_sdk.tracing import TransactionKwargs
82
+
83
+ import sentry_sdk
84
+
85
+ P = ParamSpec("P")
86
+ R = TypeVar("R")
31
87
 
32
88
  F = TypeVar("F", bound=Callable[..., Any])
33
89
  T = TypeVar("T")
34
90
 
35
91
 
92
+ # Holds data that will be added to **all** events sent by this process.
93
+ # In case this is a http server (think web framework) with multiple users
94
+ # the data will be added to events of all users.
95
+ # Typically this is used for process wide data such as the release.
96
+ _global_scope = None # type: Optional[Scope]
97
+
98
+ # Holds data for the active request.
99
+ # This is used to isolate data for different requests or users.
100
+ # The isolation scope is usually created by integrations, but may also
101
+ # be created manually
102
+ _isolation_scope = ContextVar("isolation_scope", default=None)
103
+
104
+ # Holds data for the active span.
105
+ # This can be used to manually add additional data to a span.
106
+ _current_scope = ContextVar("current_scope", default=None)
107
+
36
108
  global_event_processors = [] # type: List[EventProcessor]
37
109
 
110
+ # A function returning a (trace_id, span_id) tuple
111
+ # from an external tracing source (such as otel)
112
+ _external_propagation_context_fn = None # type: Optional[Callable[[], Optional[Tuple[str, str]]]]
113
+
114
+
115
+ class ScopeType(Enum):
116
+ CURRENT = "current"
117
+ ISOLATION = "isolation"
118
+ GLOBAL = "global"
119
+ MERGED = "merged"
120
+
121
+
122
+ class _ScopeManager:
123
+ def __init__(self, hub=None):
124
+ # type: (Optional[Any]) -> None
125
+ self._old_scopes = [] # type: List[Scope]
126
+
127
+ def __enter__(self):
128
+ # type: () -> Scope
129
+ isolation_scope = Scope.get_isolation_scope()
130
+
131
+ self._old_scopes.append(isolation_scope)
132
+
133
+ forked_scope = isolation_scope.fork()
134
+ _isolation_scope.set(forked_scope)
135
+
136
+ return forked_scope
137
+
138
+ def __exit__(self, exc_type, exc_value, tb):
139
+ # type: (Any, Any, Any) -> None
140
+ old_scope = self._old_scopes.pop()
141
+ _isolation_scope.set(old_scope)
142
+
38
143
 
39
144
  def add_global_event_processor(processor):
40
145
  # type: (EventProcessor) -> None
41
146
  global_event_processors.append(processor)
42
147
 
43
148
 
149
+ def register_external_propagation_context(fn):
150
+ # type: (Callable[[], Optional[Tuple[str, str]]]) -> None
151
+ global _external_propagation_context_fn
152
+ _external_propagation_context_fn = fn
153
+
154
+
155
+ def remove_external_propagation_context():
156
+ # type: () -> None
157
+ global _external_propagation_context_fn
158
+ _external_propagation_context_fn = None
159
+
160
+
161
+ def get_external_propagation_context():
162
+ # type: () -> Optional[Tuple[str, str]]
163
+ return (
164
+ _external_propagation_context_fn() if _external_propagation_context_fn else None
165
+ )
166
+
167
+
44
168
  def _attr_setter(fn):
45
169
  # type: (Any) -> Any
46
170
  return property(fset=fn, doc=fn.__doc__)
@@ -62,7 +186,7 @@ def _disable_capture(fn):
62
186
  return wrapper # type: ignore
63
187
 
64
188
 
65
- class Scope(object):
189
+ class Scope:
66
190
  """The scope holds extra information that should be sent with all
67
191
  events that belong to it.
68
192
  """
@@ -77,56 +201,569 @@ class Scope(object):
77
201
  "_level",
78
202
  "_name",
79
203
  "_fingerprint",
204
+ # note that for legacy reasons, _transaction is the transaction *name*,
205
+ # not a Transaction object (the object is stored in _span)
80
206
  "_transaction",
207
+ "_transaction_info",
81
208
  "_user",
82
209
  "_tags",
83
210
  "_contexts",
84
211
  "_extras",
85
212
  "_breadcrumbs",
213
+ "_n_breadcrumbs_truncated",
214
+ "_gen_ai_original_message_count",
86
215
  "_event_processors",
87
216
  "_error_processors",
88
217
  "_should_capture",
89
218
  "_span",
90
219
  "_session",
220
+ "_attachments",
91
221
  "_force_auto_session_tracking",
222
+ "_profile",
223
+ "_propagation_context",
224
+ "client",
225
+ "_type",
226
+ "_last_event_id",
227
+ "_flags",
92
228
  )
93
229
 
94
- def __init__(self):
95
- # type: () -> None
230
+ def __init__(self, ty=None, client=None):
231
+ # type: (Optional[ScopeType], Optional[sentry_sdk.Client]) -> None
232
+ self._type = ty
233
+
96
234
  self._event_processors = [] # type: List[EventProcessor]
97
235
  self._error_processors = [] # type: List[ErrorProcessor]
98
236
 
99
237
  self._name = None # type: Optional[str]
238
+ self._propagation_context = None # type: Optional[PropagationContext]
239
+ self._n_breadcrumbs_truncated = 0 # type: int
240
+ self._gen_ai_original_message_count = {} # type: Dict[str, int]
241
+
242
+ self.client = NonRecordingClient() # type: sentry_sdk.client.BaseClient
243
+
244
+ if client is not None:
245
+ self.set_client(client)
246
+
100
247
  self.clear()
101
248
 
249
+ incoming_trace_information = self._load_trace_data_from_env()
250
+ self.generate_propagation_context(incoming_data=incoming_trace_information)
251
+
252
+ def __copy__(self):
253
+ # type: () -> Scope
254
+ """
255
+ Returns a copy of this scope.
256
+ This also creates a copy of all referenced data structures.
257
+ """
258
+ rv = object.__new__(self.__class__) # type: Scope
259
+
260
+ rv._type = self._type
261
+ rv.client = self.client
262
+ rv._level = self._level
263
+ rv._name = self._name
264
+ rv._fingerprint = self._fingerprint
265
+ rv._transaction = self._transaction
266
+ rv._transaction_info = self._transaction_info.copy()
267
+ rv._user = self._user
268
+
269
+ rv._tags = self._tags.copy()
270
+ rv._contexts = self._contexts.copy()
271
+ rv._extras = self._extras.copy()
272
+
273
+ rv._breadcrumbs = copy(self._breadcrumbs)
274
+ rv._n_breadcrumbs_truncated = self._n_breadcrumbs_truncated
275
+ rv._gen_ai_original_message_count = self._gen_ai_original_message_count.copy()
276
+ rv._event_processors = self._event_processors.copy()
277
+ rv._error_processors = self._error_processors.copy()
278
+ rv._propagation_context = self._propagation_context
279
+
280
+ rv._should_capture = self._should_capture
281
+ rv._span = self._span
282
+ rv._session = self._session
283
+ rv._force_auto_session_tracking = self._force_auto_session_tracking
284
+ rv._attachments = self._attachments.copy()
285
+
286
+ rv._profile = self._profile
287
+
288
+ rv._last_event_id = self._last_event_id
289
+
290
+ rv._flags = deepcopy(self._flags)
291
+
292
+ return rv
293
+
294
+ @classmethod
295
+ def get_current_scope(cls):
296
+ # type: () -> Scope
297
+ """
298
+ .. versionadded:: 2.0.0
299
+
300
+ Returns the current scope.
301
+ """
302
+ current_scope = _current_scope.get()
303
+ if current_scope is None:
304
+ current_scope = Scope(ty=ScopeType.CURRENT)
305
+ _current_scope.set(current_scope)
306
+
307
+ return current_scope
308
+
309
+ @classmethod
310
+ def set_current_scope(cls, new_current_scope):
311
+ # type: (Scope) -> None
312
+ """
313
+ .. versionadded:: 2.0.0
314
+
315
+ Sets the given scope as the new current scope overwriting the existing current scope.
316
+ :param new_current_scope: The scope to set as the new current scope.
317
+ """
318
+ _current_scope.set(new_current_scope)
319
+
320
+ @classmethod
321
+ def get_isolation_scope(cls):
322
+ # type: () -> Scope
323
+ """
324
+ .. versionadded:: 2.0.0
325
+
326
+ Returns the isolation scope.
327
+ """
328
+ isolation_scope = _isolation_scope.get()
329
+ if isolation_scope is None:
330
+ isolation_scope = Scope(ty=ScopeType.ISOLATION)
331
+ _isolation_scope.set(isolation_scope)
332
+
333
+ return isolation_scope
334
+
335
+ @classmethod
336
+ def set_isolation_scope(cls, new_isolation_scope):
337
+ # type: (Scope) -> None
338
+ """
339
+ .. versionadded:: 2.0.0
340
+
341
+ Sets the given scope as the new isolation scope overwriting the existing isolation scope.
342
+ :param new_isolation_scope: The scope to set as the new isolation scope.
343
+ """
344
+ _isolation_scope.set(new_isolation_scope)
345
+
346
+ @classmethod
347
+ def get_global_scope(cls):
348
+ # type: () -> Scope
349
+ """
350
+ .. versionadded:: 2.0.0
351
+
352
+ Returns the global scope.
353
+ """
354
+ global _global_scope
355
+ if _global_scope is None:
356
+ _global_scope = Scope(ty=ScopeType.GLOBAL)
357
+
358
+ return _global_scope
359
+
360
+ @classmethod
361
+ def last_event_id(cls):
362
+ # type: () -> Optional[str]
363
+ """
364
+ .. versionadded:: 2.2.0
365
+
366
+ Returns event ID of the event most recently captured by the isolation scope, or None if no event
367
+ has been captured. We do not consider events that are dropped, e.g. by a before_send hook.
368
+ Transactions also are not considered events in this context.
369
+
370
+ The event corresponding to the returned event ID is NOT guaranteed to actually be sent to Sentry;
371
+ whether the event is sent depends on the transport. The event could be sent later or not at all.
372
+ Even a sent event could fail to arrive in Sentry due to network issues, exhausted quotas, or
373
+ various other reasons.
374
+ """
375
+ return cls.get_isolation_scope()._last_event_id
376
+
377
+ def _merge_scopes(self, additional_scope=None, additional_scope_kwargs=None):
378
+ # type: (Optional[Scope], Optional[Dict[str, Any]]) -> Scope
379
+ """
380
+ Merges global, isolation and current scope into a new scope and
381
+ adds the given additional scope or additional scope kwargs to it.
382
+ """
383
+ if additional_scope and additional_scope_kwargs:
384
+ raise TypeError("cannot provide scope and kwargs")
385
+
386
+ final_scope = copy(_global_scope) if _global_scope is not None else Scope()
387
+ final_scope._type = ScopeType.MERGED
388
+
389
+ isolation_scope = _isolation_scope.get()
390
+ if isolation_scope is not None:
391
+ final_scope.update_from_scope(isolation_scope)
392
+
393
+ current_scope = _current_scope.get()
394
+ if current_scope is not None:
395
+ final_scope.update_from_scope(current_scope)
396
+
397
+ if self != current_scope and self != isolation_scope:
398
+ final_scope.update_from_scope(self)
399
+
400
+ if additional_scope is not None:
401
+ if callable(additional_scope):
402
+ additional_scope(final_scope)
403
+ else:
404
+ final_scope.update_from_scope(additional_scope)
405
+
406
+ elif additional_scope_kwargs:
407
+ final_scope.update_from_kwargs(**additional_scope_kwargs)
408
+
409
+ return final_scope
410
+
411
+ @classmethod
412
+ def get_client(cls):
413
+ # type: () -> sentry_sdk.client.BaseClient
414
+ """
415
+ .. versionadded:: 2.0.0
416
+
417
+ Returns the currently used :py:class:`sentry_sdk.Client`.
418
+ This checks the current scope, the isolation scope and the global scope for a client.
419
+ If no client is available a :py:class:`sentry_sdk.client.NonRecordingClient` is returned.
420
+ """
421
+ current_scope = _current_scope.get()
422
+ try:
423
+ client = current_scope.client
424
+ except AttributeError:
425
+ client = None
426
+
427
+ if client is not None and client.is_active():
428
+ return client
429
+
430
+ isolation_scope = _isolation_scope.get()
431
+ try:
432
+ client = isolation_scope.client
433
+ except AttributeError:
434
+ client = None
435
+
436
+ if client is not None and client.is_active():
437
+ return client
438
+
439
+ try:
440
+ client = _global_scope.client # type: ignore
441
+ except AttributeError:
442
+ client = None
443
+
444
+ if client is not None and client.is_active():
445
+ return client
446
+
447
+ return NonRecordingClient()
448
+
449
+ def set_client(self, client=None):
450
+ # type: (Optional[sentry_sdk.client.BaseClient]) -> None
451
+ """
452
+ .. versionadded:: 2.0.0
453
+
454
+ Sets the client for this scope.
455
+
456
+ :param client: The client to use in this scope.
457
+ If `None` the client of the scope will be replaced by a :py:class:`sentry_sdk.NonRecordingClient`.
458
+
459
+ """
460
+ self.client = client if client is not None else NonRecordingClient()
461
+
462
+ def fork(self):
463
+ # type: () -> Scope
464
+ """
465
+ .. versionadded:: 2.0.0
466
+
467
+ Returns a fork of this scope.
468
+ """
469
+ forked_scope = copy(self)
470
+ return forked_scope
471
+
472
+ def _load_trace_data_from_env(self):
473
+ # type: () -> Optional[Dict[str, str]]
474
+ """
475
+ Load Sentry trace id and baggage from environment variables.
476
+ Can be disabled by setting SENTRY_USE_ENVIRONMENT to "false".
477
+ """
478
+ incoming_trace_information = None
479
+
480
+ sentry_use_environment = (
481
+ os.environ.get("SENTRY_USE_ENVIRONMENT") or ""
482
+ ).lower()
483
+ use_environment = sentry_use_environment not in FALSE_VALUES
484
+ if use_environment:
485
+ incoming_trace_information = {}
486
+
487
+ if os.environ.get("SENTRY_TRACE"):
488
+ incoming_trace_information[SENTRY_TRACE_HEADER_NAME] = (
489
+ os.environ.get("SENTRY_TRACE") or ""
490
+ )
491
+
492
+ if os.environ.get("SENTRY_BAGGAGE"):
493
+ incoming_trace_information[BAGGAGE_HEADER_NAME] = (
494
+ os.environ.get("SENTRY_BAGGAGE") or ""
495
+ )
496
+
497
+ return incoming_trace_information or None
498
+
499
+ def set_new_propagation_context(self):
500
+ # type: () -> None
501
+ """
502
+ Creates a new propagation context and sets it as `_propagation_context`. Overwriting existing one.
503
+ """
504
+ self._propagation_context = PropagationContext()
505
+
506
+ def generate_propagation_context(self, incoming_data=None):
507
+ # type: (Optional[Dict[str, str]]) -> None
508
+ """
509
+ Makes sure the propagation context is set on the scope.
510
+ If there is `incoming_data` overwrite existing propagation context.
511
+ If there is no `incoming_data` create new propagation context, but do NOT overwrite if already existing.
512
+ """
513
+ if incoming_data:
514
+ propagation_context = PropagationContext.from_incoming_data(incoming_data)
515
+ if propagation_context is not None:
516
+ self._propagation_context = propagation_context
517
+
518
+ if self._type != ScopeType.CURRENT:
519
+ if self._propagation_context is None:
520
+ self.set_new_propagation_context()
521
+
522
+ def get_dynamic_sampling_context(self):
523
+ # type: () -> Optional[Dict[str, str]]
524
+ """
525
+ Returns the Dynamic Sampling Context from the Propagation Context.
526
+ If not existing, creates a new one.
527
+ """
528
+ if self._propagation_context is None:
529
+ return None
530
+
531
+ baggage = self.get_baggage()
532
+ if baggage is not None:
533
+ self._propagation_context.dynamic_sampling_context = (
534
+ baggage.dynamic_sampling_context()
535
+ )
536
+
537
+ return self._propagation_context.dynamic_sampling_context
538
+
539
+ def get_traceparent(self, *args, **kwargs):
540
+ # type: (Any, Any) -> Optional[str]
541
+ """
542
+ Returns the Sentry "sentry-trace" header (aka the traceparent) from the
543
+ currently active span or the scopes Propagation Context.
544
+ """
545
+ client = self.get_client()
546
+
547
+ # If we have an active span, return traceparent from there
548
+ if has_tracing_enabled(client.options) and self.span is not None:
549
+ return self.span.to_traceparent()
550
+
551
+ # If this scope has a propagation context, return traceparent from there
552
+ if self._propagation_context is not None:
553
+ traceparent = "%s-%s" % (
554
+ self._propagation_context.trace_id,
555
+ self._propagation_context.span_id,
556
+ )
557
+ return traceparent
558
+
559
+ # Fall back to isolation scope's traceparent. It always has one
560
+ return self.get_isolation_scope().get_traceparent()
561
+
562
+ def get_baggage(self, *args, **kwargs):
563
+ # type: (Any, Any) -> Optional[Baggage]
564
+ """
565
+ Returns the Sentry "baggage" header containing trace information from the
566
+ currently active span or the scopes Propagation Context.
567
+ """
568
+ client = self.get_client()
569
+
570
+ # If we have an active span, return baggage from there
571
+ if has_tracing_enabled(client.options) and self.span is not None:
572
+ return self.span.to_baggage()
573
+
574
+ # If this scope has a propagation context, return baggage from there
575
+ if self._propagation_context is not None:
576
+ dynamic_sampling_context = (
577
+ self._propagation_context.dynamic_sampling_context
578
+ )
579
+ if dynamic_sampling_context is None:
580
+ return Baggage.from_options(self)
581
+ else:
582
+ return Baggage(dynamic_sampling_context)
583
+
584
+ # Fall back to isolation scope's baggage. It always has one
585
+ return self.get_isolation_scope().get_baggage()
586
+
587
+ def get_trace_context(self):
588
+ # type: () -> Dict[str, Any]
589
+ """
590
+ Returns the Sentry "trace" context from the Propagation Context.
591
+ """
592
+ if has_tracing_enabled(self.get_client().options) and self._span is not None:
593
+ return self._span.get_trace_context()
594
+
595
+ # if we are tracing externally (otel), those values take precedence
596
+ external_propagation_context = get_external_propagation_context()
597
+ if external_propagation_context:
598
+ trace_id, span_id = external_propagation_context
599
+ return {"trace_id": trace_id, "span_id": span_id}
600
+
601
+ propagation_context = self.get_active_propagation_context()
602
+ if propagation_context is None:
603
+ return {}
604
+
605
+ return {
606
+ "trace_id": propagation_context.trace_id,
607
+ "span_id": propagation_context.span_id,
608
+ "parent_span_id": propagation_context.parent_span_id,
609
+ "dynamic_sampling_context": self.get_dynamic_sampling_context(),
610
+ }
611
+
612
+ def trace_propagation_meta(self, *args, **kwargs):
613
+ # type: (*Any, **Any) -> str
614
+ """
615
+ Return meta tags which should be injected into HTML templates
616
+ to allow propagation of trace information.
617
+ """
618
+ span = kwargs.pop("span", None)
619
+ if span is not None:
620
+ logger.warning(
621
+ "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future."
622
+ )
623
+
624
+ meta = ""
625
+
626
+ sentry_trace = self.get_traceparent()
627
+ if sentry_trace is not None:
628
+ meta += '<meta name="%s" content="%s">' % (
629
+ SENTRY_TRACE_HEADER_NAME,
630
+ sentry_trace,
631
+ )
632
+
633
+ baggage = self.get_baggage()
634
+ if baggage is not None:
635
+ meta += '<meta name="%s" content="%s">' % (
636
+ BAGGAGE_HEADER_NAME,
637
+ baggage.serialize(),
638
+ )
639
+
640
+ return meta
641
+
642
+ def iter_headers(self):
643
+ # type: () -> Iterator[Tuple[str, str]]
644
+ """
645
+ Creates a generator which returns the `sentry-trace` and `baggage` headers from the Propagation Context.
646
+ """
647
+ if self._propagation_context is not None:
648
+ traceparent = self.get_traceparent()
649
+ if traceparent is not None:
650
+ yield SENTRY_TRACE_HEADER_NAME, traceparent
651
+
652
+ dsc = self.get_dynamic_sampling_context()
653
+ if dsc is not None:
654
+ baggage = Baggage(dsc).serialize()
655
+ yield BAGGAGE_HEADER_NAME, baggage
656
+
657
+ def iter_trace_propagation_headers(self, *args, **kwargs):
658
+ # type: (Any, Any) -> Generator[Tuple[str, str], None, None]
659
+ """
660
+ Return HTTP headers which allow propagation of trace data.
661
+
662
+ If a span is given, the trace data will taken from the span.
663
+ If no span is given, the trace data is taken from the scope.
664
+ """
665
+ client = self.get_client()
666
+ if not client.options.get("propagate_traces"):
667
+ warnings.warn(
668
+ "The `propagate_traces` parameter is deprecated. Please use `trace_propagation_targets` instead.",
669
+ DeprecationWarning,
670
+ stacklevel=2,
671
+ )
672
+ return
673
+
674
+ span = kwargs.pop("span", None)
675
+ span = span or self.span
676
+
677
+ if has_tracing_enabled(client.options) and span is not None:
678
+ for header in span.iter_headers():
679
+ yield header
680
+ else:
681
+ # If this scope has a propagation context, return headers from there
682
+ # (it could be that self is not the current scope nor the isolation scope)
683
+ if self._propagation_context is not None:
684
+ for header in self.iter_headers():
685
+ yield header
686
+ else:
687
+ # otherwise try headers from current scope
688
+ current_scope = self.get_current_scope()
689
+ if current_scope._propagation_context is not None:
690
+ for header in current_scope.iter_headers():
691
+ yield header
692
+ else:
693
+ # otherwise fall back to headers from isolation scope
694
+ isolation_scope = self.get_isolation_scope()
695
+ if isolation_scope._propagation_context is not None:
696
+ for header in isolation_scope.iter_headers():
697
+ yield header
698
+
699
+ def get_active_propagation_context(self):
700
+ # type: () -> Optional[PropagationContext]
701
+ if self._propagation_context is not None:
702
+ return self._propagation_context
703
+
704
+ current_scope = self.get_current_scope()
705
+ if current_scope._propagation_context is not None:
706
+ return current_scope._propagation_context
707
+
708
+ isolation_scope = self.get_isolation_scope()
709
+ if isolation_scope._propagation_context is not None:
710
+ return isolation_scope._propagation_context
711
+
712
+ return None
713
+
102
714
  def clear(self):
103
715
  # type: () -> None
104
716
  """Clears the entire scope."""
105
- self._level = None # type: Optional[str]
717
+ self._level = None # type: Optional[LogLevelStr]
106
718
  self._fingerprint = None # type: Optional[List[str]]
107
719
  self._transaction = None # type: Optional[str]
720
+ self._transaction_info = {} # type: dict[str, str]
108
721
  self._user = None # type: Optional[Dict[str, Any]]
109
722
 
110
723
  self._tags = {} # type: Dict[str, Any]
111
724
  self._contexts = {} # type: Dict[str, Dict[str, Any]]
112
- self._extras = {} # type: Dict[str, Any]
725
+ self._extras = {} # type: dict[str, Any]
726
+ self._attachments = [] # type: List[Attachment]
113
727
 
114
728
  self.clear_breadcrumbs()
115
- self._should_capture = True
729
+ self._should_capture = True # type: bool
116
730
 
117
731
  self._span = None # type: Optional[Span]
118
732
  self._session = None # type: Optional[Session]
119
733
  self._force_auto_session_tracking = None # type: Optional[bool]
120
734
 
735
+ self._profile = None # type: Optional[Profile]
736
+
737
+ self._propagation_context = None
738
+
739
+ # self._last_event_id is only applicable to isolation scopes
740
+ self._last_event_id = None # type: Optional[str]
741
+ self._flags = None # type: Optional[FlagBuffer]
742
+
121
743
  @_attr_setter
122
744
  def level(self, value):
123
- # type: (Optional[str]) -> None
124
- """When set this overrides the level. Deprecated in favor of set_level."""
745
+ # type: (LogLevelStr) -> None
746
+ """
747
+ When set this overrides the level.
748
+
749
+ .. deprecated:: 1.0.0
750
+ Use :func:`set_level` instead.
751
+
752
+ :param value: The level to set.
753
+ """
754
+ logger.warning(
755
+ "Deprecated: use .set_level() instead. This will be removed in the future."
756
+ )
757
+
125
758
  self._level = value
126
759
 
127
760
  def set_level(self, value):
128
- # type: (Optional[str]) -> None
129
- """Sets the level for the scope."""
761
+ # type: (LogLevelStr) -> None
762
+ """
763
+ Sets the level for the scope.
764
+
765
+ :param value: The level to set.
766
+ """
130
767
  self._level = value
131
768
 
132
769
  @_attr_setter
@@ -139,45 +776,76 @@ class Scope(object):
139
776
  def transaction(self):
140
777
  # type: () -> Any
141
778
  # would be type: () -> Optional[Transaction], see https://github.com/python/mypy/issues/3004
142
- """Return the transaction (root span) in the scope."""
143
- if self._span is None or self._span._span_recorder is None:
779
+ """Return the transaction (root span) in the scope, if any."""
780
+
781
+ # there is no span/transaction on the scope
782
+ if self._span is None:
144
783
  return None
145
- try:
146
- return self._span._span_recorder.spans[0]
147
- except (AttributeError, IndexError):
784
+
785
+ # there is an orphan span on the scope
786
+ if self._span.containing_transaction is None:
148
787
  return None
149
788
 
789
+ # there is either a transaction (which is its own containing
790
+ # transaction) or a non-orphan span on the scope
791
+ return self._span.containing_transaction
792
+
150
793
  @transaction.setter
151
794
  def transaction(self, value):
152
795
  # type: (Any) -> None
153
796
  # would be type: (Optional[str]) -> None, see https://github.com/python/mypy/issues/3004
154
- """When set this forces a specific transaction name to be set."""
797
+ """When set this forces a specific transaction name to be set.
798
+
799
+ Deprecated: use set_transaction_name instead."""
800
+
155
801
  # XXX: the docstring above is misleading. The implementation of
156
802
  # apply_to_event prefers an existing value of event.transaction over
157
803
  # anything set in the scope.
158
804
  # XXX: note that with the introduction of the Scope.transaction getter,
159
805
  # there is a semantic and type mismatch between getter and setter. The
160
- # getter returns a transaction, the setter sets a transaction name.
806
+ # getter returns a Transaction, the setter sets a transaction name.
161
807
  # Without breaking version compatibility, we could make the setter set a
162
808
  # transaction name or transaction (self._span) depending on the type of
163
809
  # the value argument.
810
+
811
+ logger.warning(
812
+ "Assigning to scope.transaction directly is deprecated: use scope.set_transaction_name() instead."
813
+ )
164
814
  self._transaction = value
165
- span = self._span
166
- if span and isinstance(span, Transaction):
167
- span.name = value
815
+ if self._span and self._span.containing_transaction:
816
+ self._span.containing_transaction.name = value
817
+
818
+ def set_transaction_name(self, name, source=None):
819
+ # type: (str, Optional[str]) -> None
820
+ """Set the transaction name and optionally the transaction source."""
821
+ self._transaction = name
822
+
823
+ if self._span and self._span.containing_transaction:
824
+ self._span.containing_transaction.name = name
825
+ if source:
826
+ self._span.containing_transaction.source = source
827
+
828
+ if source:
829
+ self._transaction_info["source"] = source
168
830
 
169
831
  @_attr_setter
170
832
  def user(self, value):
171
- # type: (Dict[str, Any]) -> None
833
+ # type: (Optional[Dict[str, Any]]) -> None
172
834
  """When set a specific user is bound to the scope. Deprecated in favor of set_user."""
835
+ warnings.warn(
836
+ "The `Scope.user` setter is deprecated in favor of `Scope.set_user()`.",
837
+ DeprecationWarning,
838
+ stacklevel=2,
839
+ )
173
840
  self.set_user(value)
174
841
 
175
842
  def set_user(self, value):
176
- # type: (Dict[str, Any]) -> None
843
+ # type: (Optional[Dict[str, Any]]) -> None
177
844
  """Sets a user for the scope."""
178
845
  self._user = value
179
- if self._session is not None:
180
- self._session.update(user=value)
846
+ session = self.get_isolation_scope()._session
847
+ if session is not None:
848
+ session.update(user=value)
181
849
 
182
850
  @property
183
851
  def span(self):
@@ -195,34 +863,73 @@ class Scope(object):
195
863
  transaction = span
196
864
  if transaction.name:
197
865
  self._transaction = transaction.name
866
+ if transaction.source:
867
+ self._transaction_info["source"] = transaction.source
198
868
 
199
- def set_tag(
200
- self,
201
- key, # type: str
202
- value, # type: Any
203
- ):
204
- # type: (...) -> None
205
- """Sets a tag for a key to a specific value."""
869
+ @property
870
+ def profile(self):
871
+ # type: () -> Optional[Profile]
872
+ return self._profile
873
+
874
+ @profile.setter
875
+ def profile(self, profile):
876
+ # type: (Optional[Profile]) -> None
877
+
878
+ self._profile = profile
879
+
880
+ def set_tag(self, key, value):
881
+ # type: (str, Any) -> None
882
+ """
883
+ Sets a tag for a key to a specific value.
884
+
885
+ :param key: Key of the tag to set.
886
+
887
+ :param value: Value of the tag to set.
888
+ """
206
889
  self._tags[key] = value
207
890
 
208
- def remove_tag(
209
- self, key # type: str
210
- ):
211
- # type: (...) -> None
212
- """Removes a specific tag."""
891
+ def set_tags(self, tags):
892
+ # type: (Mapping[str, object]) -> None
893
+ """Sets multiple tags at once.
894
+
895
+ This method updates multiple tags at once. The tags are passed as a dictionary
896
+ or other mapping type.
897
+
898
+ Calling this method is equivalent to calling `set_tag` on each key-value pair
899
+ in the mapping. If a tag key already exists in the scope, its value will be
900
+ updated. If the tag key does not exist in the scope, the key-value pair will
901
+ be added to the scope.
902
+
903
+ This method only modifies tag keys in the `tags` mapping passed to the method.
904
+ `scope.set_tags({})` is, therefore, a no-op.
905
+
906
+ :param tags: A mapping of tag keys to tag values to set.
907
+ """
908
+ self._tags.update(tags)
909
+
910
+ def remove_tag(self, key):
911
+ # type: (str) -> None
912
+ """
913
+ Removes a specific tag.
914
+
915
+ :param key: Key of the tag to remove.
916
+ """
213
917
  self._tags.pop(key, None)
214
918
 
215
919
  def set_context(
216
920
  self,
217
921
  key, # type: str
218
- value, # type: Any
922
+ value, # type: Dict[str, Any]
219
923
  ):
220
924
  # type: (...) -> None
221
- """Binds a context at a certain key to a specific value."""
925
+ """
926
+ Binds a context at a certain key to a specific value.
927
+ """
222
928
  self._contexts[key] = value
223
929
 
224
930
  def remove_context(
225
- self, key # type: str
931
+ self,
932
+ key, # type: str
226
933
  ):
227
934
  # type: (...) -> None
228
935
  """Removes a context."""
@@ -238,7 +945,8 @@ class Scope(object):
238
945
  self._extras[key] = value
239
946
 
240
947
  def remove_extra(
241
- self, key # type: str
948
+ self,
949
+ key, # type: str
242
950
  ):
243
951
  # type: (...) -> None
244
952
  """Removes a specific extra key."""
@@ -248,9 +956,409 @@ class Scope(object):
248
956
  # type: () -> None
249
957
  """Clears breadcrumb buffer."""
250
958
  self._breadcrumbs = deque() # type: Deque[Breadcrumb]
959
+ self._n_breadcrumbs_truncated = 0
960
+
961
+ def add_attachment(
962
+ self,
963
+ bytes=None, # type: Union[None, bytes, Callable[[], bytes]]
964
+ filename=None, # type: Optional[str]
965
+ path=None, # type: Optional[str]
966
+ content_type=None, # type: Optional[str]
967
+ add_to_transactions=False, # type: bool
968
+ ):
969
+ # type: (...) -> None
970
+ """Adds an attachment to future events sent from this scope.
971
+
972
+ The parameters are the same as for the :py:class:`sentry_sdk.attachments.Attachment` constructor.
973
+ """
974
+ self._attachments.append(
975
+ Attachment(
976
+ bytes=bytes,
977
+ path=path,
978
+ filename=filename,
979
+ content_type=content_type,
980
+ add_to_transactions=add_to_transactions,
981
+ )
982
+ )
983
+
984
+ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
985
+ # type: (Optional[Breadcrumb], Optional[BreadcrumbHint], Any) -> None
986
+ """
987
+ Adds a breadcrumb.
988
+
989
+ :param crumb: Dictionary with the data as the sentry v7/v8 protocol expects.
990
+
991
+ :param hint: An optional value that can be used by `before_breadcrumb`
992
+ to customize the breadcrumbs that are emitted.
993
+ """
994
+ client = self.get_client()
995
+
996
+ if not client.is_active():
997
+ logger.info("Dropped breadcrumb because no client bound")
998
+ return
999
+
1000
+ before_breadcrumb = client.options.get("before_breadcrumb")
1001
+ max_breadcrumbs = client.options.get("max_breadcrumbs", DEFAULT_MAX_BREADCRUMBS)
1002
+
1003
+ crumb = dict(crumb or ()) # type: Breadcrumb
1004
+ crumb.update(kwargs)
1005
+ if not crumb:
1006
+ return
1007
+
1008
+ hint = dict(hint or ()) # type: Hint
1009
+
1010
+ if crumb.get("timestamp") is None:
1011
+ crumb["timestamp"] = datetime.now(timezone.utc)
1012
+ if crumb.get("type") is None:
1013
+ crumb["type"] = "default"
1014
+
1015
+ if before_breadcrumb is not None:
1016
+ new_crumb = before_breadcrumb(crumb, hint)
1017
+ else:
1018
+ new_crumb = crumb
1019
+
1020
+ if new_crumb is not None:
1021
+ self._breadcrumbs.append(new_crumb)
1022
+ else:
1023
+ logger.info("before breadcrumb dropped breadcrumb (%s)", crumb)
1024
+
1025
+ while len(self._breadcrumbs) > max_breadcrumbs:
1026
+ self._breadcrumbs.popleft()
1027
+ self._n_breadcrumbs_truncated += 1
1028
+
1029
+ def start_transaction(
1030
+ self,
1031
+ transaction=None,
1032
+ instrumenter=INSTRUMENTER.SENTRY,
1033
+ custom_sampling_context=None,
1034
+ **kwargs,
1035
+ ):
1036
+ # type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan]
1037
+ """
1038
+ Start and return a transaction.
1039
+
1040
+ Start an existing transaction if given, otherwise create and start a new
1041
+ transaction with kwargs.
1042
+
1043
+ This is the entry point to manual tracing instrumentation.
1044
+
1045
+ A tree structure can be built by adding child spans to the transaction,
1046
+ and child spans to other spans. To start a new child span within the
1047
+ transaction or any span, call the respective `.start_child()` method.
1048
+
1049
+ Every child span must be finished before the transaction is finished,
1050
+ otherwise the unfinished spans are discarded.
1051
+
1052
+ When used as context managers, spans and transactions are automatically
1053
+ finished at the end of the `with` block. If not using context managers,
1054
+ call the `.finish()` method.
1055
+
1056
+ When the transaction is finished, it will be sent to Sentry with all its
1057
+ finished child spans.
1058
+
1059
+ :param transaction: The transaction to start. If omitted, we create and
1060
+ start a new transaction.
1061
+ :param instrumenter: This parameter is meant for internal use only. It
1062
+ will be removed in the next major version.
1063
+ :param custom_sampling_context: The transaction's custom sampling context.
1064
+ :param kwargs: Optional keyword arguments to be passed to the Transaction
1065
+ constructor. See :py:class:`sentry_sdk.tracing.Transaction` for
1066
+ available arguments.
1067
+ """
1068
+ kwargs.setdefault("scope", self)
1069
+
1070
+ client = self.get_client()
1071
+
1072
+ configuration_instrumenter = client.options["instrumenter"]
1073
+
1074
+ if instrumenter != configuration_instrumenter:
1075
+ return NoOpSpan()
1076
+
1077
+ try_autostart_continuous_profiler()
1078
+
1079
+ custom_sampling_context = custom_sampling_context or {}
1080
+
1081
+ # kwargs at this point has type TransactionKwargs, since we have removed
1082
+ # the client and custom_sampling_context from it.
1083
+ transaction_kwargs = kwargs # type: TransactionKwargs
1084
+
1085
+ # if we haven't been given a transaction, make one
1086
+ if transaction is None:
1087
+ transaction = Transaction(**transaction_kwargs)
1088
+
1089
+ # use traces_sample_rate, traces_sampler, and/or inheritance to make a
1090
+ # sampling decision
1091
+ sampling_context = {
1092
+ "transaction_context": transaction.to_json(),
1093
+ "parent_sampled": transaction.parent_sampled,
1094
+ }
1095
+ sampling_context.update(custom_sampling_context)
1096
+ transaction._set_initial_sampling_decision(sampling_context=sampling_context)
1097
+
1098
+ # update the sample rate in the dsc
1099
+ if transaction.sample_rate is not None:
1100
+ propagation_context = self.get_active_propagation_context()
1101
+ if propagation_context:
1102
+ dsc = propagation_context.dynamic_sampling_context
1103
+ if dsc is not None:
1104
+ dsc["sample_rate"] = str(transaction.sample_rate)
1105
+ if transaction._baggage:
1106
+ transaction._baggage.sentry_items["sample_rate"] = str(
1107
+ transaction.sample_rate
1108
+ )
1109
+
1110
+ if transaction.sampled:
1111
+ profile = Profile(
1112
+ transaction.sampled, transaction._start_timestamp_monotonic_ns
1113
+ )
1114
+ profile._set_initial_sampling_decision(sampling_context=sampling_context)
1115
+
1116
+ transaction._profile = profile
1117
+
1118
+ transaction._continuous_profile = try_profile_lifecycle_trace_start()
1119
+
1120
+ # Typically, the profiler is set when the transaction is created. But when
1121
+ # using the auto lifecycle, the profiler isn't running when the first
1122
+ # transaction is started. So make sure we update the profiler id on it.
1123
+ if transaction._continuous_profile is not None:
1124
+ transaction.set_profiler_id(get_profiler_id())
1125
+
1126
+ # we don't bother to keep spans if we already know we're not going to
1127
+ # send the transaction
1128
+ max_spans = (client.options["_experiments"].get("max_spans")) or 1000
1129
+ transaction.init_span_recorder(maxlen=max_spans)
1130
+
1131
+ return transaction
1132
+
1133
+ def start_span(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
1134
+ # type: (str, Any) -> Span
1135
+ """
1136
+ Start a span whose parent is the currently active span or transaction, if any.
1137
+
1138
+ The return value is a :py:class:`sentry_sdk.tracing.Span` instance,
1139
+ typically used as a context manager to start and stop timing in a `with`
1140
+ block.
1141
+
1142
+ Only spans contained in a transaction are sent to Sentry. Most
1143
+ integrations start a transaction at the appropriate time, for example
1144
+ for every incoming HTTP request. Use
1145
+ :py:meth:`sentry_sdk.start_transaction` to start a new transaction when
1146
+ one is not already in progress.
1147
+
1148
+ For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
1149
+
1150
+ The instrumenter parameter is deprecated for user code, and it will
1151
+ be removed in the next major version. Going forward, it should only
1152
+ be used by the SDK itself.
1153
+ """
1154
+ if kwargs.get("description") is not None:
1155
+ warnings.warn(
1156
+ "The `description` parameter is deprecated. Please use `name` instead.",
1157
+ DeprecationWarning,
1158
+ stacklevel=2,
1159
+ )
1160
+
1161
+ with new_scope():
1162
+ kwargs.setdefault("scope", self)
1163
+
1164
+ client = self.get_client()
1165
+
1166
+ configuration_instrumenter = client.options["instrumenter"]
1167
+
1168
+ if instrumenter != configuration_instrumenter:
1169
+ return NoOpSpan()
1170
+
1171
+ # get current span or transaction
1172
+ span = self.span or self.get_isolation_scope().span
1173
+
1174
+ if span is None:
1175
+ # New spans get the `trace_id` from the scope
1176
+ if "trace_id" not in kwargs:
1177
+ propagation_context = self.get_active_propagation_context()
1178
+ if propagation_context is not None:
1179
+ kwargs["trace_id"] = propagation_context.trace_id
1180
+
1181
+ span = Span(**kwargs)
1182
+ else:
1183
+ # Children take `trace_id`` from the parent span.
1184
+ span = span.start_child(**kwargs)
1185
+
1186
+ return span
1187
+
1188
+ def continue_trace(
1189
+ self, environ_or_headers, op=None, name=None, source=None, origin="manual"
1190
+ ):
1191
+ # type: (Dict[str, Any], Optional[str], Optional[str], Optional[str], str) -> Transaction
1192
+ """
1193
+ Sets the propagation context from environment or headers and returns a transaction.
1194
+ """
1195
+ self.generate_propagation_context(environ_or_headers)
1196
+
1197
+ # When we generate the propagation context, the sample_rand value is set
1198
+ # if missing or invalid (we use the original value if it's valid).
1199
+ # We want the transaction to use the same sample_rand value. Due to duplicated
1200
+ # propagation logic in the transaction, we pass it in to avoid recomputing it
1201
+ # in the transaction.
1202
+ # TYPE SAFETY: self.generate_propagation_context() ensures that self._propagation_context
1203
+ # is not None.
1204
+ sample_rand = typing.cast(
1205
+ PropagationContext, self._propagation_context
1206
+ )._sample_rand()
1207
+
1208
+ transaction = Transaction.continue_from_headers(
1209
+ normalize_incoming_data(environ_or_headers),
1210
+ _sample_rand=sample_rand,
1211
+ op=op,
1212
+ origin=origin,
1213
+ name=name,
1214
+ source=source,
1215
+ )
1216
+
1217
+ return transaction
1218
+
1219
+ def capture_event(self, event, hint=None, scope=None, **scope_kwargs):
1220
+ # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str]
1221
+ """
1222
+ Captures an event.
1223
+
1224
+ Merges given scope data and calls :py:meth:`sentry_sdk.client._Client.capture_event`.
1225
+
1226
+ :param event: A ready-made event that can be directly sent to Sentry.
1227
+
1228
+ :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.
1229
+
1230
+ :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
1231
+ The `scope` and `scope_kwargs` parameters are mutually exclusive.
1232
+
1233
+ :param scope_kwargs: Optional data to apply to event.
1234
+ For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`.
1235
+ The `scope` and `scope_kwargs` parameters are mutually exclusive.
1236
+
1237
+ :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`).
1238
+ """
1239
+ if disable_capture_event.get(False):
1240
+ return None
1241
+
1242
+ scope = self._merge_scopes(scope, scope_kwargs)
1243
+
1244
+ event_id = self.get_client().capture_event(event=event, hint=hint, scope=scope)
1245
+
1246
+ if event_id is not None and event.get("type") != "transaction":
1247
+ self.get_isolation_scope()._last_event_id = event_id
1248
+
1249
+ return event_id
1250
+
1251
+ def capture_message(self, message, level=None, scope=None, **scope_kwargs):
1252
+ # type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str]
1253
+ """
1254
+ Captures a message.
1255
+
1256
+ :param message: The string to send as the message.
1257
+
1258
+ :param level: If no level is provided, the default level is `info`.
1259
+
1260
+ :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
1261
+ The `scope` and `scope_kwargs` parameters are mutually exclusive.
1262
+
1263
+ :param scope_kwargs: Optional data to apply to event.
1264
+ For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`.
1265
+ The `scope` and `scope_kwargs` parameters are mutually exclusive.
1266
+
1267
+ :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`).
1268
+ """
1269
+ if disable_capture_event.get(False):
1270
+ return None
1271
+
1272
+ if level is None:
1273
+ level = "info"
1274
+
1275
+ event = {
1276
+ "message": message,
1277
+ "level": level,
1278
+ } # type: Event
1279
+
1280
+ return self.capture_event(event, scope=scope, **scope_kwargs)
1281
+
1282
+ def capture_exception(self, error=None, scope=None, **scope_kwargs):
1283
+ # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str]
1284
+ """Captures an exception.
1285
+
1286
+ :param error: An exception to capture. If `None`, `sys.exc_info()` will be used.
1287
+
1288
+ :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
1289
+ The `scope` and `scope_kwargs` parameters are mutually exclusive.
1290
+
1291
+ :param scope_kwargs: Optional data to apply to event.
1292
+ For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`.
1293
+ The `scope` and `scope_kwargs` parameters are mutually exclusive.
1294
+
1295
+ :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`).
1296
+ """
1297
+ if disable_capture_event.get(False):
1298
+ return None
1299
+
1300
+ if error is not None:
1301
+ exc_info = exc_info_from_error(error)
1302
+ else:
1303
+ exc_info = sys.exc_info()
1304
+
1305
+ event, hint = event_from_exception(
1306
+ exc_info, client_options=self.get_client().options
1307
+ )
1308
+
1309
+ try:
1310
+ return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs)
1311
+ except Exception:
1312
+ capture_internal_exception(sys.exc_info())
1313
+
1314
+ return None
1315
+
1316
+ def start_session(self, *args, **kwargs):
1317
+ # type: (*Any, **Any) -> None
1318
+ """Starts a new session."""
1319
+ session_mode = kwargs.pop("session_mode", "application")
1320
+
1321
+ self.end_session()
1322
+
1323
+ client = self.get_client()
1324
+ self._session = Session(
1325
+ release=client.options.get("release"),
1326
+ environment=client.options.get("environment"),
1327
+ user=self._user,
1328
+ session_mode=session_mode,
1329
+ )
1330
+
1331
+ def end_session(self, *args, **kwargs):
1332
+ # type: (*Any, **Any) -> None
1333
+ """Ends the current session if there is one."""
1334
+ session = self._session
1335
+ self._session = None
1336
+
1337
+ if session is not None:
1338
+ session.close()
1339
+ self.get_client().capture_session(session)
1340
+
1341
+ def stop_auto_session_tracking(self, *args, **kwargs):
1342
+ # type: (*Any, **Any) -> None
1343
+ """Stops automatic session tracking.
1344
+
1345
+ This temporarily session tracking for the current scope when called.
1346
+ To resume session tracking call `resume_auto_session_tracking`.
1347
+ """
1348
+ self.end_session()
1349
+ self._force_auto_session_tracking = False
1350
+
1351
+ def resume_auto_session_tracking(self):
1352
+ # type: (...) -> None
1353
+ """Resumes automatic session tracking for the current scope if
1354
+ disabled earlier. This requires that generally automatic session
1355
+ tracking is enabled.
1356
+ """
1357
+ self._force_auto_session_tracking = None
251
1358
 
252
1359
  def add_event_processor(
253
- self, func # type: EventProcessor
1360
+ self,
1361
+ func, # type: EventProcessor
254
1362
  ):
255
1363
  # type: (...) -> None
256
1364
  """Register a scope local event processor on the scope.
@@ -294,77 +1402,204 @@ class Scope(object):
294
1402
 
295
1403
  self._error_processors.append(func)
296
1404
 
297
- @_disable_capture
298
- def apply_to_event(
299
- self,
300
- event, # type: Event
301
- hint, # type: Hint
302
- ):
303
- # type: (...) -> Optional[Event]
304
- """Applies the information contained on the scope to the given event."""
305
-
306
- def _drop(event, cause, ty):
307
- # type: (Dict[str, Any], Any, str) -> Optional[Any]
308
- logger.info("%s (%s) dropped event (%s)", ty, cause, event)
309
- return None
310
-
1405
+ def _apply_level_to_event(self, event, hint, options):
1406
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
311
1407
  if self._level is not None:
312
1408
  event["level"] = self._level
313
1409
 
314
- if event.get("type") != "transaction":
315
- event.setdefault("breadcrumbs", {}).setdefault("values", []).extend(
316
- self._breadcrumbs
317
- )
1410
+ def _apply_breadcrumbs_to_event(self, event, hint, options):
1411
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
1412
+ event.setdefault("breadcrumbs", {})
1413
+
1414
+ # This check is just for mypy -
1415
+ if not isinstance(event["breadcrumbs"], AnnotatedValue):
1416
+ event["breadcrumbs"].setdefault("values", [])
1417
+ event["breadcrumbs"]["values"].extend(self._breadcrumbs)
318
1418
 
1419
+ # Attempt to sort timestamps
1420
+ try:
1421
+ if not isinstance(event["breadcrumbs"], AnnotatedValue):
1422
+ for crumb in event["breadcrumbs"]["values"]:
1423
+ if isinstance(crumb["timestamp"], str):
1424
+ crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"])
1425
+
1426
+ event["breadcrumbs"]["values"].sort(
1427
+ key=lambda crumb: crumb["timestamp"]
1428
+ )
1429
+ except Exception as err:
1430
+ logger.debug("Error when sorting breadcrumbs", exc_info=err)
1431
+ pass
1432
+
1433
+ def _apply_user_to_event(self, event, hint, options):
1434
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
319
1435
  if event.get("user") is None and self._user is not None:
320
1436
  event["user"] = self._user
321
1437
 
1438
+ def _apply_transaction_name_to_event(self, event, hint, options):
1439
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
322
1440
  if event.get("transaction") is None and self._transaction is not None:
323
1441
  event["transaction"] = self._transaction
324
1442
 
1443
+ def _apply_transaction_info_to_event(self, event, hint, options):
1444
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
1445
+ if event.get("transaction_info") is None and self._transaction_info is not None:
1446
+ event["transaction_info"] = self._transaction_info
1447
+
1448
+ def _apply_fingerprint_to_event(self, event, hint, options):
1449
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
325
1450
  if event.get("fingerprint") is None and self._fingerprint is not None:
326
1451
  event["fingerprint"] = self._fingerprint
327
1452
 
1453
+ def _apply_extra_to_event(self, event, hint, options):
1454
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
328
1455
  if self._extras:
329
1456
  event.setdefault("extra", {}).update(self._extras)
330
1457
 
1458
+ def _apply_tags_to_event(self, event, hint, options):
1459
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
331
1460
  if self._tags:
332
1461
  event.setdefault("tags", {}).update(self._tags)
333
1462
 
1463
+ def _apply_contexts_to_event(self, event, hint, options):
1464
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
334
1465
  if self._contexts:
335
1466
  event.setdefault("contexts", {}).update(self._contexts)
336
1467
 
337
- if self._span is not None:
338
- contexts = event.setdefault("contexts", {})
339
- if not contexts.get("trace"):
340
- contexts["trace"] = self._span.get_trace_context()
1468
+ contexts = event.setdefault("contexts", {})
1469
+
1470
+ # Add "trace" context
1471
+ if contexts.get("trace") is None:
1472
+ contexts["trace"] = self.get_trace_context()
1473
+
1474
+ def _apply_flags_to_event(self, event, hint, options):
1475
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
1476
+ flags = self.flags.get()
1477
+ if len(flags) > 0:
1478
+ event.setdefault("contexts", {}).setdefault("flags", {}).update(
1479
+ {"values": flags}
1480
+ )
1481
+
1482
+ def _drop(self, cause, ty):
1483
+ # type: (Any, str) -> Optional[Any]
1484
+ logger.info("%s (%s) dropped event", ty, cause)
1485
+ return None
341
1486
 
1487
+ def run_error_processors(self, event, hint):
1488
+ # type: (Event, Hint) -> Optional[Event]
1489
+ """
1490
+ Runs the error processors on the event and returns the modified event.
1491
+ """
342
1492
  exc_info = hint.get("exc_info")
343
1493
  if exc_info is not None:
344
- for error_processor in self._error_processors:
1494
+ error_processors = chain(
1495
+ self.get_global_scope()._error_processors,
1496
+ self.get_isolation_scope()._error_processors,
1497
+ self.get_current_scope()._error_processors,
1498
+ )
1499
+
1500
+ for error_processor in error_processors:
345
1501
  new_event = error_processor(event, exc_info)
346
1502
  if new_event is None:
347
- return _drop(event, error_processor, "error processor")
1503
+ return self._drop(error_processor, "error processor")
1504
+
348
1505
  event = new_event
349
1506
 
350
- for event_processor in chain(global_event_processors, self._event_processors):
351
- new_event = event
352
- with capture_internal_exceptions():
353
- new_event = event_processor(event, hint)
354
- if new_event is None:
355
- return _drop(event, event_processor, "event processor")
356
- event = new_event
1507
+ return event
1508
+
1509
+ def run_event_processors(self, event, hint):
1510
+ # type: (Event, Hint) -> Optional[Event]
1511
+ """
1512
+ Runs the event processors on the event and returns the modified event.
1513
+ """
1514
+ ty = event.get("type")
1515
+ is_check_in = ty == "check_in"
1516
+
1517
+ if not is_check_in:
1518
+ # Get scopes without creating them to prevent infinite recursion
1519
+ isolation_scope = _isolation_scope.get()
1520
+ current_scope = _current_scope.get()
1521
+
1522
+ event_processors = chain(
1523
+ global_event_processors,
1524
+ _global_scope and _global_scope._event_processors or [],
1525
+ isolation_scope and isolation_scope._event_processors or [],
1526
+ current_scope and current_scope._event_processors or [],
1527
+ )
1528
+
1529
+ for event_processor in event_processors:
1530
+ new_event = event
1531
+ with capture_internal_exceptions():
1532
+ new_event = event_processor(event, hint)
1533
+ if new_event is None:
1534
+ return self._drop(event_processor, "event processor")
1535
+ event = new_event
1536
+
1537
+ return event
1538
+
1539
+ @_disable_capture
1540
+ def apply_to_event(
1541
+ self,
1542
+ event, # type: Event
1543
+ hint, # type: Hint
1544
+ options=None, # type: Optional[Dict[str, Any]]
1545
+ ):
1546
+ # type: (...) -> Optional[Event]
1547
+ """Applies the information contained on the scope to the given event."""
1548
+ ty = event.get("type")
1549
+ is_transaction = ty == "transaction"
1550
+ is_check_in = ty == "check_in"
1551
+
1552
+ # put all attachments into the hint. This lets callbacks play around
1553
+ # with attachments. We also later pull this out of the hint when we
1554
+ # create the envelope.
1555
+ attachments_to_send = hint.get("attachments") or []
1556
+ for attachment in self._attachments:
1557
+ if not is_transaction or attachment.add_to_transactions:
1558
+ attachments_to_send.append(attachment)
1559
+ hint["attachments"] = attachments_to_send
1560
+
1561
+ self._apply_contexts_to_event(event, hint, options)
1562
+
1563
+ if is_check_in:
1564
+ # Check-ins only support the trace context, strip all others
1565
+ event["contexts"] = {
1566
+ "trace": event.setdefault("contexts", {}).get("trace", {})
1567
+ }
1568
+
1569
+ if not is_check_in:
1570
+ self._apply_level_to_event(event, hint, options)
1571
+ self._apply_fingerprint_to_event(event, hint, options)
1572
+ self._apply_user_to_event(event, hint, options)
1573
+ self._apply_transaction_name_to_event(event, hint, options)
1574
+ self._apply_transaction_info_to_event(event, hint, options)
1575
+ self._apply_tags_to_event(event, hint, options)
1576
+ self._apply_extra_to_event(event, hint, options)
1577
+
1578
+ if not is_transaction and not is_check_in:
1579
+ self._apply_breadcrumbs_to_event(event, hint, options)
1580
+ self._apply_flags_to_event(event, hint, options)
1581
+
1582
+ event = self.run_error_processors(event, hint)
1583
+ if event is None:
1584
+ return None
1585
+
1586
+ event = self.run_event_processors(event, hint)
1587
+ if event is None:
1588
+ return None
357
1589
 
358
1590
  return event
359
1591
 
360
1592
  def update_from_scope(self, scope):
361
1593
  # type: (Scope) -> None
1594
+ """Update the scope with another scope's data."""
362
1595
  if scope._level is not None:
363
1596
  self._level = scope._level
364
1597
  if scope._fingerprint is not None:
365
1598
  self._fingerprint = scope._fingerprint
366
1599
  if scope._transaction is not None:
367
1600
  self._transaction = scope._transaction
1601
+ if scope._transaction_info is not None:
1602
+ self._transaction_info.update(scope._transaction_info)
368
1603
  if scope._user is not None:
369
1604
  self._user = scope._user
370
1605
  if scope._tags:
@@ -375,19 +1610,42 @@ class Scope(object):
375
1610
  self._extras.update(scope._extras)
376
1611
  if scope._breadcrumbs:
377
1612
  self._breadcrumbs.extend(scope._breadcrumbs)
1613
+ if scope._n_breadcrumbs_truncated:
1614
+ self._n_breadcrumbs_truncated = (
1615
+ self._n_breadcrumbs_truncated + scope._n_breadcrumbs_truncated
1616
+ )
1617
+ if scope._gen_ai_original_message_count:
1618
+ self._gen_ai_original_message_count.update(
1619
+ scope._gen_ai_original_message_count
1620
+ )
378
1621
  if scope._span:
379
1622
  self._span = scope._span
1623
+ if scope._attachments:
1624
+ self._attachments.extend(scope._attachments)
1625
+ if scope._profile:
1626
+ self._profile = scope._profile
1627
+ if scope._propagation_context:
1628
+ self._propagation_context = scope._propagation_context
1629
+ if scope._session:
1630
+ self._session = scope._session
1631
+ if scope._flags:
1632
+ if not self._flags:
1633
+ self._flags = deepcopy(scope._flags)
1634
+ else:
1635
+ for flag in scope._flags.get():
1636
+ self._flags.set(flag["flag"], flag["result"])
380
1637
 
381
1638
  def update_from_kwargs(
382
1639
  self,
383
1640
  user=None, # type: Optional[Any]
384
- level=None, # type: Optional[str]
1641
+ level=None, # type: Optional[LogLevelStr]
385
1642
  extras=None, # type: Optional[Dict[str, Any]]
386
- contexts=None, # type: Optional[Dict[str, Any]]
1643
+ contexts=None, # type: Optional[Dict[str, Dict[str, Any]]]
387
1644
  tags=None, # type: Optional[Dict[str, str]]
388
1645
  fingerprint=None, # type: Optional[List[str]]
389
1646
  ):
390
1647
  # type: (...) -> None
1648
+ """Update the scope's attributes."""
391
1649
  if level is not None:
392
1650
  self._level = level
393
1651
  if user is not None:
@@ -401,35 +1659,207 @@ class Scope(object):
401
1659
  if fingerprint is not None:
402
1660
  self._fingerprint = fingerprint
403
1661
 
404
- def __copy__(self):
405
- # type: () -> Scope
406
- rv = object.__new__(self.__class__) # type: Scope
407
-
408
- rv._level = self._level
409
- rv._name = self._name
410
- rv._fingerprint = self._fingerprint
411
- rv._transaction = self._transaction
412
- rv._user = self._user
413
-
414
- rv._tags = dict(self._tags)
415
- rv._contexts = dict(self._contexts)
416
- rv._extras = dict(self._extras)
417
-
418
- rv._breadcrumbs = copy(self._breadcrumbs)
419
- rv._event_processors = list(self._event_processors)
420
- rv._error_processors = list(self._error_processors)
421
-
422
- rv._should_capture = self._should_capture
423
- rv._span = self._span
424
- rv._session = self._session
425
- rv._force_auto_session_tracking = self._force_auto_session_tracking
426
-
427
- return rv
428
-
429
1662
  def __repr__(self):
430
1663
  # type: () -> str
431
- return "<%s id=%s name=%s>" % (
1664
+ return "<%s id=%s name=%s type=%s>" % (
432
1665
  self.__class__.__name__,
433
1666
  hex(id(self)),
434
1667
  self._name,
1668
+ self._type,
435
1669
  )
1670
+
1671
+ @property
1672
+ def flags(self):
1673
+ # type: () -> FlagBuffer
1674
+ if self._flags is None:
1675
+ max_flags = (
1676
+ self.get_client().options["_experiments"].get("max_flags")
1677
+ or DEFAULT_FLAG_CAPACITY
1678
+ )
1679
+ self._flags = FlagBuffer(capacity=max_flags)
1680
+ return self._flags
1681
+
1682
+
1683
+ @contextmanager
1684
+ def new_scope():
1685
+ # type: () -> Generator[Scope, None, None]
1686
+ """
1687
+ .. versionadded:: 2.0.0
1688
+
1689
+ Context manager that forks the current scope and runs the wrapped code in it.
1690
+ After the wrapped code is executed, the original scope is restored.
1691
+
1692
+ Example Usage:
1693
+
1694
+ .. code-block:: python
1695
+
1696
+ import sentry_sdk
1697
+
1698
+ with sentry_sdk.new_scope() as scope:
1699
+ scope.set_tag("color", "green")
1700
+ sentry_sdk.capture_message("hello") # will include `color` tag.
1701
+
1702
+ sentry_sdk.capture_message("hello, again") # will NOT include `color` tag.
1703
+
1704
+ """
1705
+ # fork current scope
1706
+ current_scope = Scope.get_current_scope()
1707
+ new_scope = current_scope.fork()
1708
+ token = _current_scope.set(new_scope)
1709
+
1710
+ try:
1711
+ yield new_scope
1712
+
1713
+ finally:
1714
+ try:
1715
+ # restore original scope
1716
+ _current_scope.reset(token)
1717
+ except (LookupError, ValueError):
1718
+ capture_internal_exception(sys.exc_info())
1719
+
1720
+
1721
+ @contextmanager
1722
+ def use_scope(scope):
1723
+ # type: (Scope) -> Generator[Scope, None, None]
1724
+ """
1725
+ .. versionadded:: 2.0.0
1726
+
1727
+ Context manager that uses the given `scope` and runs the wrapped code in it.
1728
+ After the wrapped code is executed, the original scope is restored.
1729
+
1730
+ Example Usage:
1731
+ Suppose the variable `scope` contains a `Scope` object, which is not currently
1732
+ the active scope.
1733
+
1734
+ .. code-block:: python
1735
+
1736
+ import sentry_sdk
1737
+
1738
+ with sentry_sdk.use_scope(scope):
1739
+ scope.set_tag("color", "green")
1740
+ sentry_sdk.capture_message("hello") # will include `color` tag.
1741
+
1742
+ sentry_sdk.capture_message("hello, again") # will NOT include `color` tag.
1743
+
1744
+ """
1745
+ # set given scope as current scope
1746
+ token = _current_scope.set(scope)
1747
+
1748
+ try:
1749
+ yield scope
1750
+
1751
+ finally:
1752
+ try:
1753
+ # restore original scope
1754
+ _current_scope.reset(token)
1755
+ except (LookupError, ValueError):
1756
+ capture_internal_exception(sys.exc_info())
1757
+
1758
+
1759
+ @contextmanager
1760
+ def isolation_scope():
1761
+ # type: () -> Generator[Scope, None, None]
1762
+ """
1763
+ .. versionadded:: 2.0.0
1764
+
1765
+ Context manager that forks the current isolation scope and runs the wrapped code in it.
1766
+ The current scope is also forked to not bleed data into the existing current scope.
1767
+ After the wrapped code is executed, the original scopes are restored.
1768
+
1769
+ Example Usage:
1770
+
1771
+ .. code-block:: python
1772
+
1773
+ import sentry_sdk
1774
+
1775
+ with sentry_sdk.isolation_scope() as scope:
1776
+ scope.set_tag("color", "green")
1777
+ sentry_sdk.capture_message("hello") # will include `color` tag.
1778
+
1779
+ sentry_sdk.capture_message("hello, again") # will NOT include `color` tag.
1780
+
1781
+ """
1782
+ # fork current scope
1783
+ current_scope = Scope.get_current_scope()
1784
+ forked_current_scope = current_scope.fork()
1785
+ current_token = _current_scope.set(forked_current_scope)
1786
+
1787
+ # fork isolation scope
1788
+ isolation_scope = Scope.get_isolation_scope()
1789
+ new_isolation_scope = isolation_scope.fork()
1790
+ isolation_token = _isolation_scope.set(new_isolation_scope)
1791
+
1792
+ try:
1793
+ yield new_isolation_scope
1794
+
1795
+ finally:
1796
+ # restore original scopes
1797
+ try:
1798
+ _current_scope.reset(current_token)
1799
+ except (LookupError, ValueError):
1800
+ capture_internal_exception(sys.exc_info())
1801
+
1802
+ try:
1803
+ _isolation_scope.reset(isolation_token)
1804
+ except (LookupError, ValueError):
1805
+ capture_internal_exception(sys.exc_info())
1806
+
1807
+
1808
+ @contextmanager
1809
+ def use_isolation_scope(isolation_scope):
1810
+ # type: (Scope) -> Generator[Scope, None, None]
1811
+ """
1812
+ .. versionadded:: 2.0.0
1813
+
1814
+ Context manager that uses the given `isolation_scope` and runs the wrapped code in it.
1815
+ The current scope is also forked to not bleed data into the existing current scope.
1816
+ After the wrapped code is executed, the original scopes are restored.
1817
+
1818
+ Example Usage:
1819
+
1820
+ .. code-block:: python
1821
+
1822
+ import sentry_sdk
1823
+
1824
+ with sentry_sdk.isolation_scope() as scope:
1825
+ scope.set_tag("color", "green")
1826
+ sentry_sdk.capture_message("hello") # will include `color` tag.
1827
+
1828
+ sentry_sdk.capture_message("hello, again") # will NOT include `color` tag.
1829
+
1830
+ """
1831
+ # fork current scope
1832
+ current_scope = Scope.get_current_scope()
1833
+ forked_current_scope = current_scope.fork()
1834
+ current_token = _current_scope.set(forked_current_scope)
1835
+
1836
+ # set given scope as isolation scope
1837
+ isolation_token = _isolation_scope.set(isolation_scope)
1838
+
1839
+ try:
1840
+ yield isolation_scope
1841
+
1842
+ finally:
1843
+ # restore original scopes
1844
+ try:
1845
+ _current_scope.reset(current_token)
1846
+ except (LookupError, ValueError):
1847
+ capture_internal_exception(sys.exc_info())
1848
+
1849
+ try:
1850
+ _isolation_scope.reset(isolation_token)
1851
+ except (LookupError, ValueError):
1852
+ capture_internal_exception(sys.exc_info())
1853
+
1854
+
1855
+ def should_send_default_pii():
1856
+ # type: () -> bool
1857
+ """Shortcut for `Scope.get_client().should_send_default_pii()`."""
1858
+ return Scope.get_client().should_send_default_pii()
1859
+
1860
+
1861
+ # Circular imports
1862
+ from sentry_sdk.client import NonRecordingClient
1863
+
1864
+ if TYPE_CHECKING:
1865
+ import sentry_sdk.client