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

1from copy import copy 

2from collections import deque 

3from itertools import chain 

4 

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 

10 

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 

19 

20 from sentry_sdk._types import ( 

21 Breadcrumb, 

22 Event, 

23 EventProcessor, 

24 ErrorProcessor, 

25 ExcInfo, 

26 Hint, 

27 Type, 

28 ) 

29 

30 from sentry_sdk.tracing import Span 

31 from sentry_sdk.session import Session 

32 

33 F = TypeVar("F", bound=Callable[..., Any]) 

34 T = TypeVar("T") 

35 

36 

37global_event_processors = [] # type: List[EventProcessor] 

38 

39 

40def add_global_event_processor(processor): 

41 # type: (EventProcessor) -> None 

42 global_event_processors.append(processor) 

43 

44 

45def _attr_setter(fn): 

46 # type: (Any) -> Any 

47 return property(fset=fn, doc=fn.__doc__) 

48 

49 

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 

62 

63 return wrapper # type: ignore 

64 

65 

66class Scope(object): 

67 """The scope holds extra information that should be sent with all 

68 events that belong to it. 

69 """ 

70 

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. 

76 

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 ) 

98 

99 def __init__(self): 

100 # type: () -> None 

101 self._event_processors = [] # type: List[EventProcessor] 

102 self._error_processors = [] # type: List[ErrorProcessor] 

103 

104 self._name = None # type: Optional[str] 

105 self.clear() 

106 

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]] 

115 

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] 

120 

121 self.clear_breadcrumbs() 

122 self._should_capture = True 

123 

124 self._span = None # type: Optional[Span] 

125 self._session = None # type: Optional[Session] 

126 self._force_auto_session_tracking = None # type: Optional[bool] 

127 

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 

133 

134 def set_level(self, value): 

135 # type: (Optional[str]) -> None 

136 """Sets the level for the scope.""" 

137 self._level = value 

138 

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 

144 

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.""" 

150 

151 # there is no span/transaction on the scope 

152 if self._span is None: 

153 return None 

154 

155 # there is an orphan span on the scope 

156 if self._span.containing_transaction is None: 

157 return None 

158 

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 

162 

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. 

168 

169 Deprecated: use set_transaction_name instead.""" 

170 

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. 

180 

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 

187 

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 

192 

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 

197 

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 

200 

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) 

206 

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) 

213 

214 @property 

215 def span(self): 

216 # type: () -> Optional[Span] 

217 """Get/set current tracing span or transaction.""" 

218 return self._span 

219 

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 

230 

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 

239 

240 def remove_tag( 

241 self, key # type: str 

242 ): 

243 # type: (...) -> None 

244 """Removes a specific tag.""" 

245 self._tags.pop(key, None) 

246 

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 

255 

256 def remove_context( 

257 self, key # type: str 

258 ): 

259 # type: (...) -> None 

260 """Removes a context.""" 

261 self._contexts.pop(key, None) 

262 

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 

271 

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) 

278 

279 def clear_breadcrumbs(self): 

280 # type: () -> None 

281 """Clears breadcrumb buffer.""" 

282 self._breadcrumbs = deque() # type: Deque[Breadcrumb] 

283 

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 ) 

303 

304 def add_event_processor( 

305 self, func # type: EventProcessor 

306 ): 

307 # type: (...) -> None 

308 """Register a scope local event processor on the scope. 

309 

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[:] 

318 

319 self._event_processors.append(func) 

320 

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. 

328 

329 :param func: A callback that works similar to an event processor but is invoked with the original exception info triple as second argument. 

330 

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 

336 

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 

346 

347 self._error_processors.append(func) 

348 

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.""" 

357 

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 

362 

363 is_transaction = event.get("type") == "transaction" 

364 

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 

373 

374 if self._level is not None: 

375 event["level"] = self._level 

376 

377 if not is_transaction: 

378 event.setdefault("breadcrumbs", {}).setdefault("values", []).extend( 

379 self._breadcrumbs 

380 ) 

381 

382 if event.get("user") is None and self._user is not None: 

383 event["user"] = self._user 

384 

385 if event.get("transaction") is None and self._transaction is not None: 

386 event["transaction"] = self._transaction 

387 

388 if event.get("transaction_info") is None and self._transaction_info is not None: 

389 event["transaction_info"] = self._transaction_info 

390 

391 if event.get("fingerprint") is None and self._fingerprint is not None: 

392 event["fingerprint"] = self._fingerprint 

393 

394 if self._extras: 

395 event.setdefault("extra", {}).update(self._extras) 

396 

397 if self._tags: 

398 event.setdefault("tags", {}).update(self._tags) 

399 

400 if self._contexts: 

401 event.setdefault("contexts", {}).update(self._contexts) 

402 

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() 

407 

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 

415 

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 

423 

424 return event 

425 

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) 

450 

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 

473 

474 def __copy__(self): 

475 # type: () -> Scope 

476 rv = object.__new__(self.__class__) # type: Scope 

477 

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 

484 

485 rv._tags = dict(self._tags) 

486 rv._contexts = dict(self._contexts) 

487 rv._extras = dict(self._extras) 

488 

489 rv._breadcrumbs = copy(self._breadcrumbs) 

490 rv._event_processors = list(self._event_processors) 

491 rv._error_processors = list(self._error_processors) 

492 

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) 

498 

499 return rv 

500 

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 )