1from copy import copy 2from collections import deque 3from functools import wraps 4from itertools import chain 5 6from sentry_sdk.utils import logger, capture_internal_exceptions 7from sentry_sdk._types import MYPY 8 9if MYPY: 10 from typing import Any 11 from typing import Dict 12 from typing import Optional 13 from typing import Deque 14 from typing import List 15 from typing import Callable 16 from typing import TypeVar 17 18 from sentry_sdk._types import ( 19 Breadcrumb, 20 Event, 21 EventProcessor, 22 ErrorProcessor, 23 ExcInfo, 24 Hint, 25 Type, 26 ) 27 28 from sentry_sdk.tracing import Span 29 from sentry_sdk.sessions import Session 30 31 F = TypeVar("F", bound=Callable[..., Any]) 32 T = TypeVar("T") 33 34 35global_event_processors = [] # type: List[EventProcessor] 36 37 38def add_global_event_processor(processor): 39 # type: (EventProcessor) -> None 40 global_event_processors.append(processor) 41 42 43def _attr_setter(fn): 44 # type: (Any) -> Any 45 return property(fset=fn, doc=fn.__doc__) 46 47 48def _disable_capture(fn): 49 # type: (F) -> F 50 @wraps(fn) 51 def wrapper(self, *args, **kwargs): 52 # type: (Any, *Dict[str, Any], **Any) -> Any 53 if not self._should_capture: 54 return 55 try: 56 self._should_capture = False 57 return fn(self, *args, **kwargs) 58 finally: 59 self._should_capture = True 60 61 return wrapper # type: ignore 62 63 64class Scope(object): 65 """The scope holds extra information that should be sent with all 66 events that belong to it. 67 """ 68 69 # NOTE: Even though it should not happen, the scope needs to not crash when 70 # accessed by multiple threads. It's fine if it's full of races, but those 71 # races should never make the user application crash. 72 # 73 # The same needs to hold for any accesses of the scope the SDK makes. 74 75 __slots__ = ( 76 "_level", 77 "_name", 78 "_fingerprint", 79 "_transaction", 80 "_user", 81 "_tags", 82 "_contexts", 83 "_extras", 84 "_breadcrumbs", 85 "_event_processors", 86 "_error_processors", 87 "_should_capture", 88 "_span", 89 "_session", 90 "_force_auto_session_tracking", 91 ) 92 93 def __init__(self): 94 # type: () -> None 95 self._event_processors = [] # type: List[EventProcessor] 96 self._error_processors = [] # type: List[ErrorProcessor] 97 98 self._name = None # type: Optional[str] 99 self.clear() 100 101 def clear(self): 102 # type: () -> None 103 """Clears the entire scope.""" 104 self._level = None # type: Optional[str] 105 self._fingerprint = None # type: Optional[List[str]] 106 self._transaction = None # type: Optional[str] 107 self._user = None # type: Optional[Dict[str, Any]] 108 109 self._tags = {} # type: Dict[str, Any] 110 self._contexts = {} # type: Dict[str, Dict[str, Any]] 111 self._extras = {} # type: Dict[str, Any] 112 113 self.clear_breadcrumbs() 114 self._should_capture = True 115 116 self._span = None # type: Optional[Span] 117 self._session = None # type: Optional[Session] 118 self._force_auto_session_tracking = None # type: Optional[bool] 119 120 @_attr_setter 121 def level(self, value): 122 # type: (Optional[str]) -> None 123 """When set this overrides the level. Deprecated in favor of set_level.""" 124 self._level = value 125 126 def set_level(self, value): 127 # type: (Optional[str]) -> None 128 """Sets the level for the scope.""" 129 self._level = value 130 131 @_attr_setter 132 def fingerprint(self, value): 133 # type: (Optional[List[str]]) -> None 134 """When set this overrides the default fingerprint.""" 135 self._fingerprint = value 136 137 @_attr_setter 138 def transaction(self, value): 139 # type: (Optional[str]) -> None 140 """When set this forces a specific transaction name to be set.""" 141 self._transaction = value 142 span = self._span 143 if span: 144 span.transaction = value 145 146 @_attr_setter 147 def user(self, value): 148 # type: (Dict[str, Any]) -> None 149 """When set a specific user is bound to the scope. Deprecated in favor of set_user.""" 150 self.set_user(value) 151 152 def set_user(self, value): 153 # type: (Dict[str, Any]) -> None 154 """Sets a user for the scope.""" 155 self._user = value 156 if self._session is not None: 157 self._session.update(user=value) 158 159 @property 160 def span(self): 161 # type: () -> Optional[Span] 162 """Get/set current tracing span.""" 163 return self._span 164 165 @span.setter 166 def span(self, span): 167 # type: (Optional[Span]) -> None 168 self._span = span 169 if span is not None: 170 span_transaction = span.transaction 171 if span_transaction: 172 self._transaction = span_transaction 173 174 def set_tag( 175 self, 176 key, # type: str 177 value, # type: Any 178 ): 179 # type: (...) -> None 180 """Sets a tag for a key to a specific value.""" 181 self._tags[key] = value 182 183 def remove_tag( 184 self, key # type: str 185 ): 186 # type: (...) -> None 187 """Removes a specific tag.""" 188 self._tags.pop(key, None) 189 190 def set_context( 191 self, 192 key, # type: str 193 value, # type: Any 194 ): 195 # type: (...) -> None 196 """Binds a context at a certain key to a specific value.""" 197 self._contexts[key] = value 198 199 def remove_context( 200 self, key # type: str 201 ): 202 # type: (...) -> None 203 """Removes a context.""" 204 self._contexts.pop(key, None) 205 206 def set_extra( 207 self, 208 key, # type: str 209 value, # type: Any 210 ): 211 # type: (...) -> None 212 """Sets an extra key to a specific value.""" 213 self._extras[key] = value 214 215 def remove_extra( 216 self, key # type: str 217 ): 218 # type: (...) -> None 219 """Removes a specific extra key.""" 220 self._extras.pop(key, None) 221 222 def clear_breadcrumbs(self): 223 # type: () -> None 224 """Clears breadcrumb buffer.""" 225 self._breadcrumbs = deque() # type: Deque[Breadcrumb] 226 227 def add_event_processor( 228 self, func # type: EventProcessor 229 ): 230 # type: (...) -> None 231 """Register a scope local event processor on the scope. 232 233 :param func: This function behaves like `before_send.` 234 """ 235 if len(self._event_processors) > 20: 236 logger.warning( 237 "Too many event processors on scope! Clearing list to free up some memory: %r", 238 self._event_processors, 239 ) 240 del self._event_processors[:] 241 242 self._event_processors.append(func) 243 244 def add_error_processor( 245 self, 246 func, # type: ErrorProcessor 247 cls=None, # type: Optional[Type[BaseException]] 248 ): 249 # type: (...) -> None 250 """Register a scope local error processor on the scope. 251 252 :param func: A callback that works similar to an event processor but is invoked with the original exception info triple as second argument. 253 254 :param cls: Optionally, only process exceptions of this type. 255 """ 256 if cls is not None: 257 cls_ = cls # For mypy. 258 real_func = func 259 260 def func(event, exc_info): 261 # type: (Event, ExcInfo) -> Optional[Event] 262 try: 263 is_inst = isinstance(exc_info[1], cls_) 264 except Exception: 265 is_inst = False 266 if is_inst: 267 return real_func(event, exc_info) 268 return event 269 270 self._error_processors.append(func) 271 272 @_disable_capture 273 def apply_to_event( 274 self, 275 event, # type: Event 276 hint, # type: Hint 277 ): 278 # type: (...) -> Optional[Event] 279 """Applies the information contained on the scope to the given event.""" 280 281 def _drop(event, cause, ty): 282 # type: (Dict[str, Any], Any, str) -> Optional[Any] 283 logger.info("%s (%s) dropped event (%s)", ty, cause, event) 284 return None 285 286 if self._level is not None: 287 event["level"] = self._level 288 289 if event.get("type") != "transaction": 290 event.setdefault("breadcrumbs", []).extend(self._breadcrumbs) 291 292 if event.get("user") is None and self._user is not None: 293 event["user"] = self._user 294 295 if event.get("transaction") is None and self._transaction is not None: 296 event["transaction"] = self._transaction 297 298 if event.get("fingerprint") is None and self._fingerprint is not None: 299 event["fingerprint"] = self._fingerprint 300 301 if self._extras: 302 event.setdefault("extra", {}).update(self._extras) 303 304 if self._tags: 305 event.setdefault("tags", {}).update(self._tags) 306 307 if self._contexts: 308 event.setdefault("contexts", {}).update(self._contexts) 309 310 if self._span is not None: 311 contexts = event.setdefault("contexts", {}) 312 if not contexts.get("trace"): 313 contexts["trace"] = self._span.get_trace_context() 314 315 exc_info = hint.get("exc_info") 316 if exc_info is not None: 317 for error_processor in self._error_processors: 318 new_event = error_processor(event, exc_info) 319 if new_event is None: 320 return _drop(event, error_processor, "error processor") 321 event = new_event 322 323 for event_processor in chain(global_event_processors, self._event_processors): 324 new_event = event 325 with capture_internal_exceptions(): 326 new_event = event_processor(event, hint) 327 if new_event is None: 328 return _drop(event, event_processor, "event processor") 329 event = new_event 330 331 return event 332 333 def update_from_scope(self, scope): 334 # type: (Scope) -> None 335 if scope._level is not None: 336 self._level = scope._level 337 if scope._fingerprint is not None: 338 self._fingerprint = scope._fingerprint 339 if scope._transaction is not None: 340 self._transaction = scope._transaction 341 if scope._user is not None: 342 self._user = scope._user 343 if scope._tags: 344 self._tags.update(scope._tags) 345 if scope._contexts: 346 self._contexts.update(scope._contexts) 347 if scope._extras: 348 self._extras.update(scope._extras) 349 if scope._breadcrumbs: 350 self._breadcrumbs.extend(scope._breadcrumbs) 351 if scope._span: 352 self._span = scope._span 353 354 def update_from_kwargs( 355 self, 356 user=None, # type: Optional[Any] 357 level=None, # type: Optional[str] 358 extras=None, # type: Optional[Dict[str, Any]] 359 contexts=None, # type: Optional[Dict[str, Any]] 360 tags=None, # type: Optional[Dict[str, str]] 361 fingerprint=None, # type: Optional[List[str]] 362 ): 363 # type: (...) -> None 364 if level is not None: 365 self._level = level 366 if user is not None: 367 self._user = user 368 if extras is not None: 369 self._extras.update(extras) 370 if contexts is not None: 371 self._contexts.update(contexts) 372 if tags is not None: 373 self._tags.update(tags) 374 if fingerprint is not None: 375 self._fingerprint = fingerprint 376 377 def __copy__(self): 378 # type: () -> Scope 379 rv = object.__new__(self.__class__) # type: Scope 380 381 rv._level = self._level 382 rv._name = self._name 383 rv._fingerprint = self._fingerprint 384 rv._transaction = self._transaction 385 rv._user = self._user 386 387 rv._tags = dict(self._tags) 388 rv._contexts = dict(self._contexts) 389 rv._extras = dict(self._extras) 390 391 rv._breadcrumbs = copy(self._breadcrumbs) 392 rv._event_processors = list(self._event_processors) 393 rv._error_processors = list(self._error_processors) 394 395 rv._should_capture = self._should_capture 396 rv._span = self._span 397 rv._session = self._session 398 rv._force_auto_session_tracking = self._force_auto_session_tracking 399 400 return rv 401 402 def __repr__(self): 403 # type: () -> str 404 return "<%s id=%s name=%s>" % ( 405 self.__class__.__name__, 406 hex(id(self)), 407 self._name, 408 ) 409