Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/sentry_sdk/scope.py: 27%
244 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from copy import copy
2from collections import deque
3from itertools import chain
5from sentry_sdk._functools import wraps
6from sentry_sdk._types import MYPY
7from sentry_sdk.utils import logger, capture_internal_exceptions
8from sentry_sdk.tracing import Transaction
9from sentry_sdk.attachments import Attachment
11if MYPY: 11 ↛ 12line 11 didn't jump to line 12, because the condition on line 11 was never true
12 from typing import Any
13 from typing import Dict
14 from typing import Optional
15 from typing import Deque
16 from typing import List
17 from typing import Callable
18 from typing import TypeVar
20 from sentry_sdk._types import (
21 Breadcrumb,
22 Event,
23 EventProcessor,
24 ErrorProcessor,
25 ExcInfo,
26 Hint,
27 Type,
28 )
30 from sentry_sdk.tracing import Span
31 from sentry_sdk.session import Session
33 F = TypeVar("F", bound=Callable[..., Any])
34 T = TypeVar("T")
37global_event_processors = [] # type: List[EventProcessor]
40def add_global_event_processor(processor):
41 # type: (EventProcessor) -> None
42 global_event_processors.append(processor)
45def _attr_setter(fn):
46 # type: (Any) -> Any
47 return property(fset=fn, doc=fn.__doc__)
50def _disable_capture(fn):
51 # type: (F) -> F
52 @wraps(fn)
53 def wrapper(self, *args, **kwargs):
54 # type: (Any, *Dict[str, Any], **Any) -> Any
55 if not self._should_capture:
56 return
57 try:
58 self._should_capture = False
59 return fn(self, *args, **kwargs)
60 finally:
61 self._should_capture = True
63 return wrapper # type: ignore
66class Scope(object):
67 """The scope holds extra information that should be sent with all
68 events that belong to it.
69 """
71 # NOTE: Even though it should not happen, the scope needs to not crash when
72 # accessed by multiple threads. It's fine if it's full of races, but those
73 # races should never make the user application crash.
74 #
75 # The same needs to hold for any accesses of the scope the SDK makes.
77 __slots__ = (
78 "_level",
79 "_name",
80 "_fingerprint",
81 # note that for legacy reasons, _transaction is the transaction *name*,
82 # not a Transaction object (the object is stored in _span)
83 "_transaction",
84 "_transaction_info",
85 "_user",
86 "_tags",
87 "_contexts",
88 "_extras",
89 "_breadcrumbs",
90 "_event_processors",
91 "_error_processors",
92 "_should_capture",
93 "_span",
94 "_session",
95 "_attachments",
96 "_force_auto_session_tracking",
97 )
99 def __init__(self):
100 # type: () -> None
101 self._event_processors = [] # type: List[EventProcessor]
102 self._error_processors = [] # type: List[ErrorProcessor]
104 self._name = None # type: Optional[str]
105 self.clear()
107 def clear(self):
108 # type: () -> None
109 """Clears the entire scope."""
110 self._level = None # type: Optional[str]
111 self._fingerprint = None # type: Optional[List[str]]
112 self._transaction = None # type: Optional[str]
113 self._transaction_info = {} # type: Dict[str, str]
114 self._user = None # type: Optional[Dict[str, Any]]
116 self._tags = {} # type: Dict[str, Any]
117 self._contexts = {} # type: Dict[str, Dict[str, Any]]
118 self._extras = {} # type: Dict[str, Any]
119 self._attachments = [] # type: List[Attachment]
121 self.clear_breadcrumbs()
122 self._should_capture = True
124 self._span = None # type: Optional[Span]
125 self._session = None # type: Optional[Session]
126 self._force_auto_session_tracking = None # type: Optional[bool]
128 @_attr_setter
129 def level(self, value):
130 # type: (Optional[str]) -> None
131 """When set this overrides the level. Deprecated in favor of set_level."""
132 self._level = value
134 def set_level(self, value):
135 # type: (Optional[str]) -> None
136 """Sets the level for the scope."""
137 self._level = value
139 @_attr_setter
140 def fingerprint(self, value):
141 # type: (Optional[List[str]]) -> None
142 """When set this overrides the default fingerprint."""
143 self._fingerprint = value
145 @property
146 def transaction(self):
147 # type: () -> Any
148 # would be type: () -> Optional[Transaction], see https://github.com/python/mypy/issues/3004
149 """Return the transaction (root span) in the scope, if any."""
151 # there is no span/transaction on the scope
152 if self._span is None:
153 return None
155 # there is an orphan span on the scope
156 if self._span.containing_transaction is None:
157 return None
159 # there is either a transaction (which is its own containing
160 # transaction) or a non-orphan span on the scope
161 return self._span.containing_transaction
163 @transaction.setter
164 def transaction(self, value):
165 # type: (Any) -> None
166 # would be type: (Optional[str]) -> None, see https://github.com/python/mypy/issues/3004
167 """When set this forces a specific transaction name to be set.
169 Deprecated: use set_transaction_name instead."""
171 # XXX: the docstring above is misleading. The implementation of
172 # apply_to_event prefers an existing value of event.transaction over
173 # anything set in the scope.
174 # XXX: note that with the introduction of the Scope.transaction getter,
175 # there is a semantic and type mismatch between getter and setter. The
176 # getter returns a Transaction, the setter sets a transaction name.
177 # Without breaking version compatibility, we could make the setter set a
178 # transaction name or transaction (self._span) depending on the type of
179 # the value argument.
181 logger.warning(
182 "Assigning to scope.transaction directly is deprecated: use scope.set_transaction_name() instead."
183 )
184 self._transaction = value
185 if self._span and self._span.containing_transaction:
186 self._span.containing_transaction.name = value
188 def set_transaction_name(self, name, source=None):
189 # type: (str, Optional[str]) -> None
190 """Set the transaction name and optionally the transaction source."""
191 self._transaction = name
193 if self._span and self._span.containing_transaction: 193 ↛ 194line 193 didn't jump to line 194, because the condition on line 193 was never true
194 self._span.containing_transaction.name = name
195 if source:
196 self._span.containing_transaction.source = source
198 if source: 198 ↛ exitline 198 didn't return from function 'set_transaction_name', because the condition on line 198 was never false
199 self._transaction_info["source"] = source
201 @_attr_setter
202 def user(self, value):
203 # type: (Optional[Dict[str, Any]]) -> None
204 """When set a specific user is bound to the scope. Deprecated in favor of set_user."""
205 self.set_user(value)
207 def set_user(self, value):
208 # type: (Optional[Dict[str, Any]]) -> None
209 """Sets a user for the scope."""
210 self._user = value
211 if self._session is not None:
212 self._session.update(user=value)
214 @property
215 def span(self):
216 # type: () -> Optional[Span]
217 """Get/set current tracing span or transaction."""
218 return self._span
220 @span.setter
221 def span(self, span):
222 # type: (Optional[Span]) -> None
223 self._span = span
224 # XXX: this differs from the implementation in JS, there Scope.setSpan
225 # does not set Scope._transactionName.
226 if isinstance(span, Transaction): 226 ↛ 227line 226 didn't jump to line 227, because the condition on line 226 was never true
227 transaction = span
228 if transaction.name:
229 self._transaction = transaction.name
231 def set_tag(
232 self,
233 key, # type: str
234 value, # type: Any
235 ):
236 # type: (...) -> None
237 """Sets a tag for a key to a specific value."""
238 self._tags[key] = value
240 def remove_tag(
241 self, key # type: str
242 ):
243 # type: (...) -> None
244 """Removes a specific tag."""
245 self._tags.pop(key, None)
247 def set_context(
248 self,
249 key, # type: str
250 value, # type: Dict[str, Any]
251 ):
252 # type: (...) -> None
253 """Binds a context at a certain key to a specific value."""
254 self._contexts[key] = value
256 def remove_context(
257 self, key # type: str
258 ):
259 # type: (...) -> None
260 """Removes a context."""
261 self._contexts.pop(key, None)
263 def set_extra(
264 self,
265 key, # type: str
266 value, # type: Any
267 ):
268 # type: (...) -> None
269 """Sets an extra key to a specific value."""
270 self._extras[key] = value
272 def remove_extra(
273 self, key # type: str
274 ):
275 # type: (...) -> None
276 """Removes a specific extra key."""
277 self._extras.pop(key, None)
279 def clear_breadcrumbs(self):
280 # type: () -> None
281 """Clears breadcrumb buffer."""
282 self._breadcrumbs = deque() # type: Deque[Breadcrumb]
284 def add_attachment(
285 self,
286 bytes=None, # type: Optional[bytes]
287 filename=None, # type: Optional[str]
288 path=None, # type: Optional[str]
289 content_type=None, # type: Optional[str]
290 add_to_transactions=False, # type: bool
291 ):
292 # type: (...) -> None
293 """Adds an attachment to future events sent."""
294 self._attachments.append(
295 Attachment(
296 bytes=bytes,
297 path=path,
298 filename=filename,
299 content_type=content_type,
300 add_to_transactions=add_to_transactions,
301 )
302 )
304 def add_event_processor(
305 self, func # type: EventProcessor
306 ):
307 # type: (...) -> None
308 """Register a scope local event processor on the scope.
310 :param func: This function behaves like `before_send.`
311 """
312 if len(self._event_processors) > 20:
313 logger.warning(
314 "Too many event processors on scope! Clearing list to free up some memory: %r",
315 self._event_processors,
316 )
317 del self._event_processors[:]
319 self._event_processors.append(func)
321 def add_error_processor(
322 self,
323 func, # type: ErrorProcessor
324 cls=None, # type: Optional[Type[BaseException]]
325 ):
326 # type: (...) -> None
327 """Register a scope local error processor on the scope.
329 :param func: A callback that works similar to an event processor but is invoked with the original exception info triple as second argument.
331 :param cls: Optionally, only process exceptions of this type.
332 """
333 if cls is not None:
334 cls_ = cls # For mypy.
335 real_func = func
337 def func(event, exc_info):
338 # type: (Event, ExcInfo) -> Optional[Event]
339 try:
340 is_inst = isinstance(exc_info[1], cls_)
341 except Exception:
342 is_inst = False
343 if is_inst:
344 return real_func(event, exc_info)
345 return event
347 self._error_processors.append(func)
349 @_disable_capture
350 def apply_to_event(
351 self,
352 event, # type: Event
353 hint, # type: Hint
354 ):
355 # type: (...) -> Optional[Event]
356 """Applies the information contained on the scope to the given event."""
358 def _drop(event, cause, ty):
359 # type: (Dict[str, Any], Any, str) -> Optional[Any]
360 logger.info("%s (%s) dropped event (%s)", ty, cause, event)
361 return None
363 is_transaction = event.get("type") == "transaction"
365 # put all attachments into the hint. This lets callbacks play around
366 # with attachments. We also later pull this out of the hint when we
367 # create the envelope.
368 attachments_to_send = hint.get("attachments") or []
369 for attachment in self._attachments:
370 if not is_transaction or attachment.add_to_transactions:
371 attachments_to_send.append(attachment)
372 hint["attachments"] = attachments_to_send
374 if self._level is not None:
375 event["level"] = self._level
377 if not is_transaction:
378 event.setdefault("breadcrumbs", {}).setdefault("values", []).extend(
379 self._breadcrumbs
380 )
382 if event.get("user") is None and self._user is not None:
383 event["user"] = self._user
385 if event.get("transaction") is None and self._transaction is not None:
386 event["transaction"] = self._transaction
388 if event.get("transaction_info") is None and self._transaction_info is not None:
389 event["transaction_info"] = self._transaction_info
391 if event.get("fingerprint") is None and self._fingerprint is not None:
392 event["fingerprint"] = self._fingerprint
394 if self._extras:
395 event.setdefault("extra", {}).update(self._extras)
397 if self._tags:
398 event.setdefault("tags", {}).update(self._tags)
400 if self._contexts:
401 event.setdefault("contexts", {}).update(self._contexts)
403 if self._span is not None:
404 contexts = event.setdefault("contexts", {})
405 if not contexts.get("trace"):
406 contexts["trace"] = self._span.get_trace_context()
408 exc_info = hint.get("exc_info")
409 if exc_info is not None:
410 for error_processor in self._error_processors:
411 new_event = error_processor(event, exc_info)
412 if new_event is None:
413 return _drop(event, error_processor, "error processor")
414 event = new_event
416 for event_processor in chain(global_event_processors, self._event_processors):
417 new_event = event
418 with capture_internal_exceptions():
419 new_event = event_processor(event, hint)
420 if new_event is None:
421 return _drop(event, event_processor, "event processor")
422 event = new_event
424 return event
426 def update_from_scope(self, scope):
427 # type: (Scope) -> None
428 if scope._level is not None:
429 self._level = scope._level
430 if scope._fingerprint is not None:
431 self._fingerprint = scope._fingerprint
432 if scope._transaction is not None:
433 self._transaction = scope._transaction
434 if scope._transaction_info is not None:
435 self._transaction_info.update(scope._transaction_info)
436 if scope._user is not None:
437 self._user = scope._user
438 if scope._tags:
439 self._tags.update(scope._tags)
440 if scope._contexts:
441 self._contexts.update(scope._contexts)
442 if scope._extras:
443 self._extras.update(scope._extras)
444 if scope._breadcrumbs:
445 self._breadcrumbs.extend(scope._breadcrumbs)
446 if scope._span:
447 self._span = scope._span
448 if scope._attachments:
449 self._attachments.extend(scope._attachments)
451 def update_from_kwargs(
452 self,
453 user=None, # type: Optional[Any]
454 level=None, # type: Optional[str]
455 extras=None, # type: Optional[Dict[str, Any]]
456 contexts=None, # type: Optional[Dict[str, Any]]
457 tags=None, # type: Optional[Dict[str, str]]
458 fingerprint=None, # type: Optional[List[str]]
459 ):
460 # type: (...) -> None
461 if level is not None:
462 self._level = level
463 if user is not None:
464 self._user = user
465 if extras is not None:
466 self._extras.update(extras)
467 if contexts is not None:
468 self._contexts.update(contexts)
469 if tags is not None:
470 self._tags.update(tags)
471 if fingerprint is not None:
472 self._fingerprint = fingerprint
474 def __copy__(self):
475 # type: () -> Scope
476 rv = object.__new__(self.__class__) # type: Scope
478 rv._level = self._level
479 rv._name = self._name
480 rv._fingerprint = self._fingerprint
481 rv._transaction = self._transaction
482 rv._transaction_info = dict(self._transaction_info)
483 rv._user = self._user
485 rv._tags = dict(self._tags)
486 rv._contexts = dict(self._contexts)
487 rv._extras = dict(self._extras)
489 rv._breadcrumbs = copy(self._breadcrumbs)
490 rv._event_processors = list(self._event_processors)
491 rv._error_processors = list(self._error_processors)
493 rv._should_capture = self._should_capture
494 rv._span = self._span
495 rv._session = self._session
496 rv._force_auto_session_tracking = self._force_auto_session_tracking
497 rv._attachments = list(self._attachments)
499 return rv
501 def __repr__(self):
502 # type: () -> str
503 return "<%s id=%s name=%s>" % (
504 self.__class__.__name__,
505 hex(id(self)),
506 self._name,
507 )