You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1828 regels
62 KiB

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