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/scope.py CHANGED
@@ -1,32 +1,177 @@
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
3
9
  from functools import wraps
4
10
  from itertools import chain
5
11
 
6
- from sentry_sdk.utils import logger, capture_internal_exceptions, object_to_json
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
7
52
 
8
- if False:
9
53
  from typing import Any
10
54
  from typing import Callable
11
- from typing import Dict
12
- from typing import Optional
13
55
  from typing import Deque
56
+ from typing import Dict
57
+ from typing import Generator
58
+ from typing import Iterator
14
59
  from typing import List
60
+ from typing import Optional
61
+ from typing import ParamSpec
62
+ from typing import Tuple
63
+ from typing import TypeVar
64
+ from typing import Union
65
+
66
+ from typing_extensions import Unpack
67
+
68
+ from sentry_sdk._types import (
69
+ Breadcrumb,
70
+ BreadcrumbHint,
71
+ ErrorProcessor,
72
+ Event,
73
+ EventProcessor,
74
+ ExcInfo,
75
+ Hint,
76
+ LogLevelStr,
77
+ SamplingContext,
78
+ Type,
79
+ )
80
+
81
+ from sentry_sdk.tracing import TransactionKwargs
82
+
83
+ import sentry_sdk
84
+
85
+ P = ParamSpec("P")
86
+ R = TypeVar("R")
87
+
88
+ F = TypeVar("F", bound=Callable[..., Any])
89
+ T = TypeVar("T")
90
+
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]
15
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)
16
103
 
17
- global_event_processors = []
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
+
108
+ global_event_processors = [] # type: List[EventProcessor]
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)
18
142
 
19
143
 
20
144
  def add_global_event_processor(processor):
21
- # type: (Callable) -> None
145
+ # type: (EventProcessor) -> None
22
146
  global_event_processors.append(processor)
23
147
 
24
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
+
25
168
  def _attr_setter(fn):
169
+ # type: (Any) -> Any
26
170
  return property(fset=fn, doc=fn.__doc__)
27
171
 
28
172
 
29
173
  def _disable_capture(fn):
174
+ # type: (F) -> F
30
175
  @wraps(fn)
31
176
  def wrapper(self, *args, **kwargs):
32
177
  # type: (Any, *Dict[str, Any], **Any) -> Any
@@ -38,117 +183,1217 @@ def _disable_capture(fn):
38
183
  finally:
39
184
  self._should_capture = True
40
185
 
41
- return wrapper
186
+ return wrapper # type: ignore
42
187
 
43
188
 
44
- class Scope(object):
189
+ class Scope:
45
190
  """The scope holds extra information that should be sent with all
46
191
  events that belong to it.
47
192
  """
48
193
 
194
+ # NOTE: Even though it should not happen, the scope needs to not crash when
195
+ # accessed by multiple threads. It's fine if it's full of races, but those
196
+ # races should never make the user application crash.
197
+ #
198
+ # The same needs to hold for any accesses of the scope the SDK makes.
199
+
49
200
  __slots__ = (
50
201
  "_level",
51
202
  "_name",
52
203
  "_fingerprint",
204
+ # note that for legacy reasons, _transaction is the transaction *name*,
205
+ # not a Transaction object (the object is stored in _span)
53
206
  "_transaction",
207
+ "_transaction_info",
54
208
  "_user",
55
209
  "_tags",
56
210
  "_contexts",
57
211
  "_extras",
58
212
  "_breadcrumbs",
213
+ "_n_breadcrumbs_truncated",
214
+ "_gen_ai_original_message_count",
59
215
  "_event_processors",
60
216
  "_error_processors",
61
217
  "_should_capture",
218
+ "_span",
219
+ "_session",
220
+ "_attachments",
221
+ "_force_auto_session_tracking",
222
+ "_profile",
223
+ "_propagation_context",
224
+ "client",
225
+ "_type",
226
+ "_last_event_id",
227
+ "_flags",
62
228
  )
63
229
 
64
- def __init__(self):
65
- self._event_processors = [] # type: List[Callable]
66
- self._error_processors = [] # type: List[Callable]
230
+ def __init__(self, ty=None, client=None):
231
+ # type: (Optional[ScopeType], Optional[sentry_sdk.Client]) -> None
232
+ self._type = ty
233
+
234
+ self._event_processors = [] # type: List[EventProcessor]
235
+ self._error_processors = [] # type: List[ErrorProcessor]
236
+
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)
67
246
 
68
- self._name = None
69
247
  self.clear()
70
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
+
714
+ def clear(self):
715
+ # type: () -> None
716
+ """Clears the entire scope."""
717
+ self._level = None # type: Optional[LogLevelStr]
718
+ self._fingerprint = None # type: Optional[List[str]]
719
+ self._transaction = None # type: Optional[str]
720
+ self._transaction_info = {} # type: dict[str, str]
721
+ self._user = None # type: Optional[Dict[str, Any]]
722
+
723
+ self._tags = {} # type: Dict[str, Any]
724
+ self._contexts = {} # type: Dict[str, Dict[str, Any]]
725
+ self._extras = {} # type: dict[str, Any]
726
+ self._attachments = [] # type: List[Attachment]
727
+
728
+ self.clear_breadcrumbs()
729
+ self._should_capture = True # type: bool
730
+
731
+ self._span = None # type: Optional[Span]
732
+ self._session = None # type: Optional[Session]
733
+ self._force_auto_session_tracking = None # type: Optional[bool]
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
+
71
743
  @_attr_setter
72
744
  def level(self, value):
73
- """When set this overrides the 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
+
758
+ self._level = value
759
+
760
+ def set_level(self, value):
761
+ # type: (LogLevelStr) -> None
762
+ """
763
+ Sets the level for the scope.
764
+
765
+ :param value: The level to set.
766
+ """
74
767
  self._level = value
75
768
 
76
769
  @_attr_setter
77
770
  def fingerprint(self, value):
771
+ # type: (Optional[List[str]]) -> None
78
772
  """When set this overrides the default fingerprint."""
79
773
  self._fingerprint = value
80
774
 
81
- @_attr_setter
775
+ @property
776
+ def transaction(self):
777
+ # type: () -> Any
778
+ # would be type: () -> Optional[Transaction], see https://github.com/python/mypy/issues/3004
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:
783
+ return None
784
+
785
+ # there is an orphan span on the scope
786
+ if self._span.containing_transaction is None:
787
+ return None
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
+
793
+ @transaction.setter
82
794
  def transaction(self, value):
83
- """When set this forces a specific transaction name to be set."""
795
+ # type: (Any) -> None
796
+ # would be type: (Optional[str]) -> None, see https://github.com/python/mypy/issues/3004
797
+ """When set this forces a specific transaction name to be set.
798
+
799
+ Deprecated: use set_transaction_name instead."""
800
+
801
+ # XXX: the docstring above is misleading. The implementation of
802
+ # apply_to_event prefers an existing value of event.transaction over
803
+ # anything set in the scope.
804
+ # XXX: note that with the introduction of the Scope.transaction getter,
805
+ # there is a semantic and type mismatch between getter and setter. The
806
+ # getter returns a Transaction, the setter sets a transaction name.
807
+ # Without breaking version compatibility, we could make the setter set a
808
+ # transaction name or transaction (self._span) depending on the type of
809
+ # the value argument.
810
+
811
+ logger.warning(
812
+ "Assigning to scope.transaction directly is deprecated: use scope.set_transaction_name() instead."
813
+ )
84
814
  self._transaction = 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
85
830
 
86
831
  @_attr_setter
87
832
  def user(self, value):
88
- """When set a specific user is bound to the scope."""
833
+ # type: (Optional[Dict[str, Any]]) -> None
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
+ )
840
+ self.set_user(value)
841
+
842
+ def set_user(self, value):
843
+ # type: (Optional[Dict[str, Any]]) -> None
844
+ """Sets a user for the scope."""
89
845
  self._user = value
846
+ session = self.get_isolation_scope()._session
847
+ if session is not None:
848
+ session.update(user=value)
849
+
850
+ @property
851
+ def span(self):
852
+ # type: () -> Optional[Span]
853
+ """Get/set current tracing span or transaction."""
854
+ return self._span
855
+
856
+ @span.setter
857
+ def span(self, span):
858
+ # type: (Optional[Span]) -> None
859
+ self._span = span
860
+ # XXX: this differs from the implementation in JS, there Scope.setSpan
861
+ # does not set Scope._transactionName.
862
+ if isinstance(span, Transaction):
863
+ transaction = span
864
+ if transaction.name:
865
+ self._transaction = transaction.name
866
+ if transaction.source:
867
+ self._transaction_info["source"] = transaction.source
868
+
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
90
879
 
91
880
  def set_tag(self, key, value):
92
- """Sets a tag for a key to a specific 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
+ """
93
889
  self._tags[key] = value
94
890
 
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
+
95
910
  def remove_tag(self, key):
96
- """Removes a specific tag."""
911
+ # type: (str) -> None
912
+ """
913
+ Removes a specific tag.
914
+
915
+ :param key: Key of the tag to remove.
916
+ """
97
917
  self._tags.pop(key, None)
98
918
 
99
- def set_context(self, key, value):
100
- """Binds a context at a certain key to a specific value."""
919
+ def set_context(
920
+ self,
921
+ key, # type: str
922
+ value, # type: Dict[str, Any]
923
+ ):
924
+ # type: (...) -> None
925
+ """
926
+ Binds a context at a certain key to a specific value.
927
+ """
101
928
  self._contexts[key] = value
102
929
 
103
- def remove_context(self, key):
930
+ def remove_context(
931
+ self,
932
+ key, # type: str
933
+ ):
934
+ # type: (...) -> None
104
935
  """Removes a context."""
105
936
  self._contexts.pop(key, None)
106
937
 
107
- def set_extra(self, key, value):
938
+ def set_extra(
939
+ self,
940
+ key, # type: str
941
+ value, # type: Any
942
+ ):
943
+ # type: (...) -> None
108
944
  """Sets an extra key to a specific value."""
109
945
  self._extras[key] = value
110
946
 
111
- def remove_extra(self, key):
947
+ def remove_extra(
948
+ self,
949
+ key, # type: str
950
+ ):
951
+ # type: (...) -> None
112
952
  """Removes a specific extra key."""
113
953
  self._extras.pop(key, None)
114
954
 
115
- def clear(self):
955
+ def clear_breadcrumbs(self):
116
956
  # type: () -> None
117
- """Clears the entire scope."""
118
- self._level = None
119
- self._fingerprint = None
120
- self._transaction = None
121
- self._user = None
957
+ """Clears breadcrumb buffer."""
958
+ self._breadcrumbs = deque() # type: Deque[Breadcrumb]
959
+ self._n_breadcrumbs_truncated = 0
122
960
 
123
- self._tags = {} # type: Dict[str, Any]
124
- self._contexts = {} # type: Dict[str, Dict]
125
- self._extras = {} # type: Dict[str, Any]
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()
126
1165
 
127
- self._breadcrumbs = deque() # type: Deque[Dict]
1166
+ configuration_instrumenter = client.options["instrumenter"]
128
1167
 
129
- self._should_capture = True
1168
+ if instrumenter != configuration_instrumenter:
1169
+ return NoOpSpan()
130
1170
 
131
- def add_event_processor(self, func):
132
- # type: (Callable) -> None
133
- """"Register a scope local event processor on the scope.
1171
+ # get current span or transaction
1172
+ span = self.span or self.get_isolation_scope().span
134
1173
 
135
- This function behaves like `before_send.`
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`).
136
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
1358
+
1359
+ def add_event_processor(
1360
+ self,
1361
+ func, # type: EventProcessor
1362
+ ):
1363
+ # type: (...) -> None
1364
+ """Register a scope local event processor on the scope.
1365
+
1366
+ :param func: This function behaves like `before_send.`
1367
+ """
1368
+ if len(self._event_processors) > 20:
1369
+ logger.warning(
1370
+ "Too many event processors on scope! Clearing list to free up some memory: %r",
1371
+ self._event_processors,
1372
+ )
1373
+ del self._event_processors[:]
1374
+
137
1375
  self._event_processors.append(func)
138
1376
 
139
- def add_error_processor(self, func, cls=None):
140
- # type: (Callable, Optional[type]) -> None
141
- """"Register a scope local error processor on the scope.
1377
+ def add_error_processor(
1378
+ self,
1379
+ func, # type: ErrorProcessor
1380
+ cls=None, # type: Optional[Type[BaseException]]
1381
+ ):
1382
+ # type: (...) -> None
1383
+ """Register a scope local error processor on the scope.
142
1384
 
143
- The error processor works similar to an event processor but is
144
- invoked with the original exception info triple as second argument.
1385
+ :param func: A callback that works similar to an event processor but is invoked with the original exception info triple as second argument.
1386
+
1387
+ :param cls: Optionally, only process exceptions of this type.
145
1388
  """
146
1389
  if cls is not None:
1390
+ cls_ = cls # For mypy.
147
1391
  real_func = func
148
1392
 
149
1393
  def func(event, exc_info):
1394
+ # type: (Event, ExcInfo) -> Optional[Event]
150
1395
  try:
151
- is_inst = isinstance(exc_info[1], cls)
1396
+ is_inst = isinstance(exc_info[1], cls_)
152
1397
  except Exception:
153
1398
  is_inst = False
154
1399
  if is_inst:
@@ -157,81 +1402,464 @@ class Scope(object):
157
1402
 
158
1403
  self._error_processors.append(func)
159
1404
 
160
- @_disable_capture
161
- def apply_to_event(self, event, hint=None):
162
- # type: (Dict[str, Any], Dict[str, Any]) -> Optional[Dict[str, Any]]
163
- """Applies the information contained on the scope to the given event."""
164
-
165
- def _drop(event, cause, ty):
166
- # type: (Dict[str, Any], Callable, str) -> Optional[Any]
167
- logger.info("%s (%s) dropped event (%s)", ty, cause, event)
168
- return None
169
-
1405
+ def _apply_level_to_event(self, event, hint, options):
1406
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
170
1407
  if self._level is not None:
171
1408
  event["level"] = self._level
172
1409
 
173
- event.setdefault("breadcrumbs", []).extend(self._breadcrumbs)
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)
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
174
1435
  if event.get("user") is None and self._user is not None:
175
1436
  event["user"] = self._user
176
1437
 
1438
+ def _apply_transaction_name_to_event(self, event, hint, options):
1439
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
177
1440
  if event.get("transaction") is None and self._transaction is not None:
178
1441
  event["transaction"] = self._transaction
179
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
180
1450
  if event.get("fingerprint") is None and self._fingerprint is not None:
181
1451
  event["fingerprint"] = self._fingerprint
182
1452
 
1453
+ def _apply_extra_to_event(self, event, hint, options):
1454
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
183
1455
  if self._extras:
184
- event.setdefault("extra", {}).update(object_to_json(self._extras))
1456
+ event.setdefault("extra", {}).update(self._extras)
185
1457
 
1458
+ def _apply_tags_to_event(self, event, hint, options):
1459
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
186
1460
  if self._tags:
187
1461
  event.setdefault("tags", {}).update(self._tags)
188
1462
 
1463
+ def _apply_contexts_to_event(self, event, hint, options):
1464
+ # type: (Event, Hint, Optional[Dict[str, Any]]) -> None
189
1465
  if self._contexts:
190
1466
  event.setdefault("contexts", {}).update(self._contexts)
191
1467
 
192
- exc_info = hint.get("exc_info") if hint is not None else None
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
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
+ """
1492
+ exc_info = hint.get("exc_info")
193
1493
  if exc_info is not None:
194
- for processor in self._error_processors:
195
- new_event = processor(event, exc_info)
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:
1501
+ new_event = error_processor(event, exc_info)
196
1502
  if new_event is None:
197
- return _drop(event, processor, "error processor")
1503
+ return self._drop(error_processor, "error processor")
1504
+
198
1505
  event = new_event
199
1506
 
200
- for processor in chain(global_event_processors, self._event_processors):
201
- new_event = event
202
- with capture_internal_exceptions():
203
- new_event = processor(event, hint)
204
- if new_event is None:
205
- return _drop(event, processor, "event processor")
206
- 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
207
1536
 
208
1537
  return event
209
1538
 
210
- def __copy__(self):
211
- # type: () -> Scope
212
- rv = object.__new__(self.__class__)
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"
213
1551
 
214
- rv._level = self._level
215
- rv._name = self._name
216
- rv._fingerprint = self._fingerprint
217
- rv._transaction = self._transaction
218
- rv._user = self._user
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
219
1560
 
220
- rv._tags = dict(self._tags)
221
- rv._contexts = dict(self._contexts)
222
- rv._extras = dict(self._extras)
1561
+ self._apply_contexts_to_event(event, hint, options)
223
1562
 
224
- rv._breadcrumbs = copy(self._breadcrumbs)
225
- rv._event_processors = list(self._event_processors)
226
- rv._error_processors = list(self._error_processors)
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
+ }
227
1568
 
228
- rv._should_capture = self._should_capture
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)
229
1577
 
230
- return rv
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
1589
+
1590
+ return event
1591
+
1592
+ def update_from_scope(self, scope):
1593
+ # type: (Scope) -> None
1594
+ """Update the scope with another scope's data."""
1595
+ if scope._level is not None:
1596
+ self._level = scope._level
1597
+ if scope._fingerprint is not None:
1598
+ self._fingerprint = scope._fingerprint
1599
+ if scope._transaction is not None:
1600
+ self._transaction = scope._transaction
1601
+ if scope._transaction_info is not None:
1602
+ self._transaction_info.update(scope._transaction_info)
1603
+ if scope._user is not None:
1604
+ self._user = scope._user
1605
+ if scope._tags:
1606
+ self._tags.update(scope._tags)
1607
+ if scope._contexts:
1608
+ self._contexts.update(scope._contexts)
1609
+ if scope._extras:
1610
+ self._extras.update(scope._extras)
1611
+ if scope._breadcrumbs:
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
+ )
1621
+ if scope._span:
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"])
1637
+
1638
+ def update_from_kwargs(
1639
+ self,
1640
+ user=None, # type: Optional[Any]
1641
+ level=None, # type: Optional[LogLevelStr]
1642
+ extras=None, # type: Optional[Dict[str, Any]]
1643
+ contexts=None, # type: Optional[Dict[str, Dict[str, Any]]]
1644
+ tags=None, # type: Optional[Dict[str, str]]
1645
+ fingerprint=None, # type: Optional[List[str]]
1646
+ ):
1647
+ # type: (...) -> None
1648
+ """Update the scope's attributes."""
1649
+ if level is not None:
1650
+ self._level = level
1651
+ if user is not None:
1652
+ self._user = user
1653
+ if extras is not None:
1654
+ self._extras.update(extras)
1655
+ if contexts is not None:
1656
+ self._contexts.update(contexts)
1657
+ if tags is not None:
1658
+ self._tags.update(tags)
1659
+ if fingerprint is not None:
1660
+ self._fingerprint = fingerprint
231
1661
 
232
1662
  def __repr__(self):
233
- return "<%s id=%s name=%s>" % (
1663
+ # type: () -> str
1664
+ return "<%s id=%s name=%s type=%s>" % (
234
1665
  self.__class__.__name__,
235
1666
  hex(id(self)),
236
1667
  self._name,
1668
+ self._type,
237
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