Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/sentry_sdk/tracing.py: 33%

293 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import uuid 

2import random 

3import time 

4 

5from datetime import datetime, timedelta 

6 

7import sentry_sdk 

8 

9from sentry_sdk.utils import logger 

10from sentry_sdk._types import MYPY 

11 

12 

13if MYPY: 13 ↛ 14line 13 didn't jump to line 14, because the condition on line 13 was never true

14 import typing 

15 

16 from typing import Optional 

17 from typing import Any 

18 from typing import Dict 

19 from typing import List 

20 from typing import Tuple 

21 from typing import Iterator 

22 

23 from sentry_sdk._types import SamplingContext, MeasurementUnit 

24 

25 

26# Transaction source 

27# see https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations 

28TRANSACTION_SOURCE_CUSTOM = "custom" 

29TRANSACTION_SOURCE_URL = "url" 

30TRANSACTION_SOURCE_ROUTE = "route" 

31TRANSACTION_SOURCE_VIEW = "view" 

32TRANSACTION_SOURCE_COMPONENT = "component" 

33TRANSACTION_SOURCE_TASK = "task" 

34TRANSACTION_SOURCE_UNKNOWN = "unknown" 

35 

36SOURCE_FOR_STYLE = { 

37 "endpoint": TRANSACTION_SOURCE_COMPONENT, 

38 "function_name": TRANSACTION_SOURCE_COMPONENT, 

39 "handler_name": TRANSACTION_SOURCE_COMPONENT, 

40 "method_and_path_pattern": TRANSACTION_SOURCE_ROUTE, 

41 "path": TRANSACTION_SOURCE_URL, 

42 "route_name": TRANSACTION_SOURCE_COMPONENT, 

43 "route_pattern": TRANSACTION_SOURCE_ROUTE, 

44 "uri_template": TRANSACTION_SOURCE_ROUTE, 

45 "url": TRANSACTION_SOURCE_ROUTE, 

46} 

47 

48 

49class _SpanRecorder(object): 

50 """Limits the number of spans recorded in a transaction.""" 

51 

52 __slots__ = ("maxlen", "spans") 

53 

54 def __init__(self, maxlen): 

55 # type: (int) -> None 

56 # FIXME: this is `maxlen - 1` only to preserve historical behavior 

57 # enforced by tests. 

58 # Either this should be changed to `maxlen` or the JS SDK implementation 

59 # should be changed to match a consistent interpretation of what maxlen 

60 # limits: either transaction+spans or only child spans. 

61 self.maxlen = maxlen - 1 

62 self.spans = [] # type: List[Span] 

63 

64 def add(self, span): 

65 # type: (Span) -> None 

66 if len(self.spans) > self.maxlen: 

67 span._span_recorder = None 

68 else: 

69 self.spans.append(span) 

70 

71 

72class Span(object): 

73 __slots__ = ( 

74 "trace_id", 

75 "span_id", 

76 "parent_span_id", 

77 "same_process_as_parent", 

78 "sampled", 

79 "op", 

80 "description", 

81 "start_timestamp", 

82 "_start_timestamp_monotonic", 

83 "status", 

84 "timestamp", 

85 "_tags", 

86 "_data", 

87 "_span_recorder", 

88 "hub", 

89 "_context_manager_state", 

90 "_containing_transaction", 

91 ) 

92 

93 def __new__(cls, **kwargs): 

94 # type: (**Any) -> Any 

95 """ 

96 Backwards-compatible implementation of Span and Transaction 

97 creation. 

98 """ 

99 

100 # TODO: consider removing this in a future release. 

101 # This is for backwards compatibility with releases before Transaction 

102 # existed, to allow for a smoother transition. 

103 if "transaction" in kwargs: 103 ↛ 104line 103 didn't jump to line 104, because the condition on line 103 was never true

104 return object.__new__(Transaction) 

105 return object.__new__(cls) 

106 

107 def __init__( 

108 self, 

109 trace_id=None, # type: Optional[str] 

110 span_id=None, # type: Optional[str] 

111 parent_span_id=None, # type: Optional[str] 

112 same_process_as_parent=True, # type: bool 

113 sampled=None, # type: Optional[bool] 

114 op=None, # type: Optional[str] 

115 description=None, # type: Optional[str] 

116 hub=None, # type: Optional[sentry_sdk.Hub] 

117 status=None, # type: Optional[str] 

118 transaction=None, # type: Optional[str] # deprecated 

119 containing_transaction=None, # type: Optional[Transaction] 

120 ): 

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

122 self.trace_id = trace_id or uuid.uuid4().hex 

123 self.span_id = span_id or uuid.uuid4().hex[16:] 

124 self.parent_span_id = parent_span_id 

125 self.same_process_as_parent = same_process_as_parent 

126 self.sampled = sampled 

127 self.op = op 

128 self.description = description 

129 self.status = status 

130 self.hub = hub 

131 self._tags = {} # type: Dict[str, str] 

132 self._data = {} # type: Dict[str, Any] 

133 self._containing_transaction = containing_transaction 

134 self.start_timestamp = datetime.utcnow() 

135 try: 

136 # TODO: For Python 3.7+, we could use a clock with ns resolution: 

137 # self._start_timestamp_monotonic = time.perf_counter_ns() 

138 

139 # Python 3.3+ 

140 self._start_timestamp_monotonic = time.perf_counter() 

141 except AttributeError: 

142 pass 

143 

144 #: End timestamp of span 

145 self.timestamp = None # type: Optional[datetime] 

146 

147 self._span_recorder = None # type: Optional[_SpanRecorder] 

148 

149 # TODO this should really live on the Transaction class rather than the Span 

150 # class 

151 def init_span_recorder(self, maxlen): 

152 # type: (int) -> None 

153 if self._span_recorder is None: 

154 self._span_recorder = _SpanRecorder(maxlen) 

155 

156 def __repr__(self): 

157 # type: () -> str 

158 return ( 

159 "<%s(op=%r, description:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r)>" 

160 % ( 

161 self.__class__.__name__, 

162 self.op, 

163 self.description, 

164 self.trace_id, 

165 self.span_id, 

166 self.parent_span_id, 

167 self.sampled, 

168 ) 

169 ) 

170 

171 def __enter__(self): 

172 # type: () -> Span 

173 hub = self.hub or sentry_sdk.Hub.current 

174 

175 _, scope = hub._stack[-1] 

176 old_span = scope.span 

177 scope.span = self 

178 self._context_manager_state = (hub, scope, old_span) 

179 return self 

180 

181 def __exit__(self, ty, value, tb): 

182 # type: (Optional[Any], Optional[Any], Optional[Any]) -> None 

183 if value is not None: 183 ↛ 184line 183 didn't jump to line 184, because the condition on line 183 was never true

184 self.set_status("internal_error") 

185 

186 hub, scope, old_span = self._context_manager_state 

187 del self._context_manager_state 

188 

189 self.finish(hub) 

190 scope.span = old_span 

191 

192 @property 

193 def containing_transaction(self): 

194 # type: () -> Optional[Transaction] 

195 

196 # this is a getter rather than a regular attribute so that transactions 

197 # can return `self` here instead (as a way to prevent them circularly 

198 # referencing themselves) 

199 return self._containing_transaction 

200 

201 def start_child(self, **kwargs): 

202 # type: (**Any) -> Span 

203 """ 

204 Start a sub-span from the current span or transaction. 

205 

206 Takes the same arguments as the initializer of :py:class:`Span`. The 

207 trace id, sampling decision, transaction pointer, and span recorder are 

208 inherited from the current span/transaction. 

209 """ 

210 kwargs.setdefault("sampled", self.sampled) 

211 

212 child = Span( 

213 trace_id=self.trace_id, 

214 parent_span_id=self.span_id, 

215 containing_transaction=self.containing_transaction, 

216 **kwargs 

217 ) 

218 

219 span_recorder = ( 

220 self.containing_transaction and self.containing_transaction._span_recorder 

221 ) 

222 if span_recorder: 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true

223 span_recorder.add(child) 

224 return child 

225 

226 def new_span(self, **kwargs): 

227 # type: (**Any) -> Span 

228 """Deprecated: use start_child instead.""" 

229 logger.warning("Deprecated: use Span.start_child instead of Span.new_span.") 

230 return self.start_child(**kwargs) 

231 

232 @classmethod 

233 def continue_from_environ( 

234 cls, 

235 environ, # type: typing.Mapping[str, str] 

236 **kwargs # type: Any 

237 ): 

238 # type: (...) -> Transaction 

239 """ 

240 Create a Transaction with the given params, then add in data pulled from 

241 the 'sentry-trace', 'baggage' and 'tracestate' headers from the environ (if any) 

242 before returning the Transaction. 

243 

244 This is different from `continue_from_headers` in that it assumes header 

245 names in the form "HTTP_HEADER_NAME" - such as you would get from a wsgi 

246 environ - rather than the form "header-name". 

247 """ 

248 if cls is Span: 

249 logger.warning( 

250 "Deprecated: use Transaction.continue_from_environ " 

251 "instead of Span.continue_from_environ." 

252 ) 

253 return Transaction.continue_from_headers(EnvironHeaders(environ), **kwargs) 

254 

255 @classmethod 

256 def continue_from_headers( 

257 cls, 

258 headers, # type: typing.Mapping[str, str] 

259 **kwargs # type: Any 

260 ): 

261 # type: (...) -> Transaction 

262 """ 

263 Create a transaction with the given params (including any data pulled from 

264 the 'sentry-trace', 'baggage' and 'tracestate' headers). 

265 """ 

266 # TODO move this to the Transaction class 

267 if cls is Span: 

268 logger.warning( 

269 "Deprecated: use Transaction.continue_from_headers " 

270 "instead of Span.continue_from_headers." 

271 ) 

272 

273 # TODO-neel move away from this kwargs stuff, it's confusing and opaque 

274 # make more explicit 

275 baggage = Baggage.from_incoming_header(headers.get("baggage")) 

276 kwargs.update({"baggage": baggage}) 

277 

278 sentrytrace_kwargs = extract_sentrytrace_data(headers.get("sentry-trace")) 

279 

280 if sentrytrace_kwargs is not None: 

281 kwargs.update(sentrytrace_kwargs) 

282 baggage.freeze 

283 

284 kwargs.update(extract_tracestate_data(headers.get("tracestate"))) 

285 

286 transaction = Transaction(**kwargs) 

287 transaction.same_process_as_parent = False 

288 

289 return transaction 

290 

291 def iter_headers(self): 

292 # type: () -> Iterator[Tuple[str, str]] 

293 """ 

294 Creates a generator which returns the span's `sentry-trace`, `baggage` and 

295 `tracestate` headers. 

296 

297 If the span's containing transaction doesn't yet have a 

298 `sentry_tracestate` value, this will cause one to be generated and 

299 stored. 

300 """ 

301 yield "sentry-trace", self.to_traceparent() 

302 

303 tracestate = self.to_tracestate() if has_tracestate_enabled(self) else None 

304 # `tracestate` will only be `None` if there's no client or no DSN 

305 # TODO (kmclb) the above will be true once the feature is no longer 

306 # behind a flag 

307 if tracestate: 307 ↛ 308line 307 didn't jump to line 308, because the condition on line 307 was never true

308 yield "tracestate", tracestate 

309 

310 if self.containing_transaction and self.containing_transaction._baggage: 310 ↛ 311line 310 didn't jump to line 311, because the condition on line 310 was never true

311 yield "baggage", self.containing_transaction._baggage.serialize() 

312 

313 @classmethod 

314 def from_traceparent( 

315 cls, 

316 traceparent, # type: Optional[str] 

317 **kwargs # type: Any 

318 ): 

319 # type: (...) -> Optional[Transaction] 

320 """ 

321 DEPRECATED: Use Transaction.continue_from_headers(headers, **kwargs) 

322 

323 Create a Transaction with the given params, then add in data pulled from 

324 the given 'sentry-trace' header value before returning the Transaction. 

325 

326 """ 

327 logger.warning( 

328 "Deprecated: Use Transaction.continue_from_headers(headers, **kwargs) " 

329 "instead of from_traceparent(traceparent, **kwargs)" 

330 ) 

331 

332 if not traceparent: 

333 return None 

334 

335 return cls.continue_from_headers({"sentry-trace": traceparent}, **kwargs) 

336 

337 def to_traceparent(self): 

338 # type: () -> str 

339 sampled = "" 

340 if self.sampled is True: 340 ↛ 341line 340 didn't jump to line 341, because the condition on line 340 was never true

341 sampled = "1" 

342 if self.sampled is False: 342 ↛ 343line 342 didn't jump to line 343, because the condition on line 342 was never true

343 sampled = "0" 

344 return "%s-%s-%s" % (self.trace_id, self.span_id, sampled) 

345 

346 def to_tracestate(self): 

347 # type: () -> Optional[str] 

348 """ 

349 Computes the `tracestate` header value using data from the containing 

350 transaction. 

351 

352 If the containing transaction doesn't yet have a `sentry_tracestate` 

353 value, this will cause one to be generated and stored. 

354 

355 If there is no containing transaction, a value will be generated but not 

356 stored. 

357 

358 Returns None if there's no client and/or no DSN. 

359 """ 

360 

361 sentry_tracestate = self.get_or_set_sentry_tracestate() 

362 third_party_tracestate = ( 

363 self.containing_transaction._third_party_tracestate 

364 if self.containing_transaction 

365 else None 

366 ) 

367 

368 if not sentry_tracestate: 

369 return None 

370 

371 header_value = sentry_tracestate 

372 

373 if third_party_tracestate: 

374 header_value = header_value + "," + third_party_tracestate 

375 

376 return header_value 

377 

378 def get_or_set_sentry_tracestate(self): 

379 # type: (Span) -> Optional[str] 

380 """ 

381 Read sentry tracestate off of the span's containing transaction. 

382 

383 If the transaction doesn't yet have a `_sentry_tracestate` value, 

384 compute one and store it. 

385 """ 

386 transaction = self.containing_transaction 

387 

388 if transaction: 

389 if not transaction._sentry_tracestate: 

390 transaction._sentry_tracestate = compute_tracestate_entry(self) 

391 

392 return transaction._sentry_tracestate 

393 

394 # orphan span - nowhere to store the value, so just return it 

395 return compute_tracestate_entry(self) 

396 

397 def set_tag(self, key, value): 

398 # type: (str, Any) -> None 

399 self._tags[key] = value 

400 

401 def set_data(self, key, value): 

402 # type: (str, Any) -> None 

403 self._data[key] = value 

404 

405 def set_status(self, value): 

406 # type: (str) -> None 

407 self.status = value 

408 

409 def set_http_status(self, http_status): 

410 # type: (int) -> None 

411 self.set_tag("http.status_code", str(http_status)) 

412 

413 if http_status < 400: 413 ↛ 415line 413 didn't jump to line 415, because the condition on line 413 was never false

414 self.set_status("ok") 

415 elif 400 <= http_status < 500: 

416 if http_status == 403: 

417 self.set_status("permission_denied") 

418 elif http_status == 404: 

419 self.set_status("not_found") 

420 elif http_status == 429: 

421 self.set_status("resource_exhausted") 

422 elif http_status == 413: 

423 self.set_status("failed_precondition") 

424 elif http_status == 401: 

425 self.set_status("unauthenticated") 

426 elif http_status == 409: 

427 self.set_status("already_exists") 

428 else: 

429 self.set_status("invalid_argument") 

430 elif 500 <= http_status < 600: 

431 if http_status == 504: 

432 self.set_status("deadline_exceeded") 

433 elif http_status == 501: 

434 self.set_status("unimplemented") 

435 elif http_status == 503: 

436 self.set_status("unavailable") 

437 else: 

438 self.set_status("internal_error") 

439 else: 

440 self.set_status("unknown_error") 

441 

442 def is_success(self): 

443 # type: () -> bool 

444 return self.status == "ok" 

445 

446 def finish(self, hub=None): 

447 # type: (Optional[sentry_sdk.Hub]) -> Optional[str] 

448 # XXX: would be type: (Optional[sentry_sdk.Hub]) -> None, but that leads 

449 # to incompatible return types for Span.finish and Transaction.finish. 

450 if self.timestamp is not None: 450 ↛ 452line 450 didn't jump to line 452, because the condition on line 450 was never true

451 # This span is already finished, ignore. 

452 return None 

453 

454 hub = hub or self.hub or sentry_sdk.Hub.current 

455 

456 try: 

457 duration_seconds = time.perf_counter() - self._start_timestamp_monotonic 

458 self.timestamp = self.start_timestamp + timedelta(seconds=duration_seconds) 

459 except AttributeError: 

460 self.timestamp = datetime.utcnow() 

461 

462 maybe_create_breadcrumbs_from_span(hub, self) 

463 return None 

464 

465 def to_json(self): 

466 # type: () -> Dict[str, Any] 

467 rv = { 

468 "trace_id": self.trace_id, 

469 "span_id": self.span_id, 

470 "parent_span_id": self.parent_span_id, 

471 "same_process_as_parent": self.same_process_as_parent, 

472 "op": self.op, 

473 "description": self.description, 

474 "start_timestamp": self.start_timestamp, 

475 "timestamp": self.timestamp, 

476 } # type: Dict[str, Any] 

477 

478 if self.status: 

479 self._tags["status"] = self.status 

480 

481 tags = self._tags 

482 if tags: 

483 rv["tags"] = tags 

484 

485 data = self._data 

486 if data: 

487 rv["data"] = data 

488 

489 return rv 

490 

491 def get_trace_context(self): 

492 # type: () -> Any 

493 rv = { 

494 "trace_id": self.trace_id, 

495 "span_id": self.span_id, 

496 "parent_span_id": self.parent_span_id, 

497 "op": self.op, 

498 "description": self.description, 

499 } # type: Dict[str, Any] 

500 if self.status: 

501 rv["status"] = self.status 

502 

503 # if the transaction didn't inherit a tracestate value, and no outgoing 

504 # requests - whose need for headers would have caused a tracestate value 

505 # to be created - were made as part of the transaction, the transaction 

506 # still won't have a tracestate value, so compute one now 

507 sentry_tracestate = self.get_or_set_sentry_tracestate() 

508 

509 if sentry_tracestate: 

510 rv["tracestate"] = sentry_tracestate 

511 

512 # TODO-neel populate fresh if head SDK 

513 if self.containing_transaction and self.containing_transaction._baggage: 

514 rv[ 

515 "dynamic_sampling_context" 

516 ] = self.containing_transaction._baggage.dynamic_sampling_context() 

517 

518 return rv 

519 

520 

521class Transaction(Span): 

522 __slots__ = ( 

523 "name", 

524 "source", 

525 "parent_sampled", 

526 # the sentry portion of the `tracestate` header used to transmit 

527 # correlation context for server-side dynamic sampling, of the form 

528 # `sentry=xxxxx`, where `xxxxx` is the base64-encoded json of the 

529 # correlation context data, missing trailing any = 

530 "_sentry_tracestate", 

531 # tracestate data from other vendors, of the form `dogs=yes,cats=maybe` 

532 "_third_party_tracestate", 

533 "_measurements", 

534 "_baggage", 

535 ) 

536 

537 def __init__( 

538 self, 

539 name="", # type: str 

540 parent_sampled=None, # type: Optional[bool] 

541 sentry_tracestate=None, # type: Optional[str] 

542 third_party_tracestate=None, # type: Optional[str] 

543 baggage=None, # type: Optional[Baggage] 

544 source=TRANSACTION_SOURCE_UNKNOWN, # type: str 

545 **kwargs # type: Any 

546 ): 

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

548 # TODO: consider removing this in a future release. 

549 # This is for backwards compatibility with releases before Transaction 

550 # existed, to allow for a smoother transition. 

551 if not name and "transaction" in kwargs: 

552 logger.warning( 

553 "Deprecated: use Transaction(name=...) to create transactions " 

554 "instead of Span(transaction=...)." 

555 ) 

556 name = kwargs.pop("transaction") 

557 Span.__init__(self, **kwargs) 

558 self.name = name 

559 self.source = source 

560 self.parent_sampled = parent_sampled 

561 # if tracestate isn't inherited and set here, it will get set lazily, 

562 # either the first time an outgoing request needs it for a header or the 

563 # first time an event needs it for inclusion in the captured data 

564 self._sentry_tracestate = sentry_tracestate 

565 self._third_party_tracestate = third_party_tracestate 

566 self._measurements = {} # type: Dict[str, Any] 

567 self._baggage = baggage 

568 

569 def __repr__(self): 

570 # type: () -> str 

571 return ( 

572 "<%s(name=%r, op=%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, source=%r)>" 

573 % ( 

574 self.__class__.__name__, 

575 self.name, 

576 self.op, 

577 self.trace_id, 

578 self.span_id, 

579 self.parent_span_id, 

580 self.sampled, 

581 self.source, 

582 ) 

583 ) 

584 

585 @property 

586 def containing_transaction(self): 

587 # type: () -> Transaction 

588 

589 # Transactions (as spans) belong to themselves (as transactions). This 

590 # is a getter rather than a regular attribute to avoid having a circular 

591 # reference. 

592 return self 

593 

594 def finish(self, hub=None): 

595 # type: (Optional[sentry_sdk.Hub]) -> Optional[str] 

596 if self.timestamp is not None: 

597 # This transaction is already finished, ignore. 

598 return None 

599 

600 hub = hub or self.hub or sentry_sdk.Hub.current 

601 client = hub.client 

602 

603 if client is None: 

604 # We have no client and therefore nowhere to send this transaction. 

605 return None 

606 

607 # This is a de facto proxy for checking if sampled = False 

608 if self._span_recorder is None: 

609 logger.debug("Discarding transaction because sampled = False") 

610 

611 # This is not entirely accurate because discards here are not 

612 # exclusively based on sample rate but also traces sampler, but 

613 # we handle this the same here. 

614 if client.transport and has_tracing_enabled(client.options): 

615 client.transport.record_lost_event( 

616 "sample_rate", data_category="transaction" 

617 ) 

618 

619 return None 

620 

621 if not self.name: 

622 logger.warning( 

623 "Transaction has no name, falling back to `<unlabeled transaction>`." 

624 ) 

625 self.name = "<unlabeled transaction>" 

626 

627 Span.finish(self, hub) 

628 

629 if not self.sampled: 

630 # At this point a `sampled = None` should have already been resolved 

631 # to a concrete decision. 

632 if self.sampled is None: 

633 logger.warning("Discarding transaction without sampling decision.") 

634 return None 

635 

636 finished_spans = [ 

637 span.to_json() 

638 for span in self._span_recorder.spans 

639 if span.timestamp is not None 

640 ] 

641 

642 # we do this to break the circular reference of transaction -> span 

643 # recorder -> span -> containing transaction (which is where we started) 

644 # before either the spans or the transaction goes out of scope and has 

645 # to be garbage collected 

646 self._span_recorder = None 

647 

648 event = { 

649 "type": "transaction", 

650 "transaction": self.name, 

651 "transaction_info": {"source": self.source}, 

652 "contexts": {"trace": self.get_trace_context()}, 

653 "tags": self._tags, 

654 "timestamp": self.timestamp, 

655 "start_timestamp": self.start_timestamp, 

656 "spans": finished_spans, 

657 } 

658 

659 if has_custom_measurements_enabled(): 

660 event["measurements"] = self._measurements 

661 

662 return hub.capture_event(event) 

663 

664 def set_measurement(self, name, value, unit=""): 

665 # type: (str, float, MeasurementUnit) -> None 

666 if not has_custom_measurements_enabled(): 

667 logger.debug( 

668 "[Tracing] Experimental custom_measurements feature is disabled" 

669 ) 

670 return 

671 

672 self._measurements[name] = {"value": value, "unit": unit} 

673 

674 def to_json(self): 

675 # type: () -> Dict[str, Any] 

676 rv = super(Transaction, self).to_json() 

677 

678 rv["name"] = self.name 

679 rv["source"] = self.source 

680 rv["sampled"] = self.sampled 

681 

682 return rv 

683 

684 def _set_initial_sampling_decision(self, sampling_context): 

685 # type: (SamplingContext) -> None 

686 """ 

687 Sets the transaction's sampling decision, according to the following 

688 precedence rules: 

689 

690 1. If a sampling decision is passed to `start_transaction` 

691 (`start_transaction(name: "my transaction", sampled: True)`), that 

692 decision will be used, regardless of anything else 

693 

694 2. If `traces_sampler` is defined, its decision will be used. It can 

695 choose to keep or ignore any parent sampling decision, or use the 

696 sampling context data to make its own decision or to choose a sample 

697 rate for the transaction. 

698 

699 3. If `traces_sampler` is not defined, but there's a parent sampling 

700 decision, the parent sampling decision will be used. 

701 

702 4. If `traces_sampler` is not defined and there's no parent sampling 

703 decision, `traces_sample_rate` will be used. 

704 """ 

705 

706 hub = self.hub or sentry_sdk.Hub.current 

707 client = hub.client 

708 options = (client and client.options) or {} 

709 transaction_description = "{op}transaction <{name}>".format( 

710 op=("<" + self.op + "> " if self.op else ""), name=self.name 

711 ) 

712 

713 # nothing to do if there's no client or if tracing is disabled 

714 if not client or not has_tracing_enabled(options): 

715 self.sampled = False 

716 return 

717 

718 # if the user has forced a sampling decision by passing a `sampled` 

719 # value when starting the transaction, go with that 

720 if self.sampled is not None: 

721 return 

722 

723 # we would have bailed already if neither `traces_sampler` nor 

724 # `traces_sample_rate` were defined, so one of these should work; prefer 

725 # the hook if so 

726 sample_rate = ( 

727 options["traces_sampler"](sampling_context) 

728 if callable(options.get("traces_sampler")) 

729 else ( 

730 # default inheritance behavior 

731 sampling_context["parent_sampled"] 

732 if sampling_context["parent_sampled"] is not None 

733 else options["traces_sample_rate"] 

734 ) 

735 ) 

736 

737 # Since this is coming from the user (or from a function provided by the 

738 # user), who knows what we might get. (The only valid values are 

739 # booleans or numbers between 0 and 1.) 

740 if not is_valid_sample_rate(sample_rate): 

741 logger.warning( 

742 "[Tracing] Discarding {transaction_description} because of invalid sample rate.".format( 

743 transaction_description=transaction_description, 

744 ) 

745 ) 

746 self.sampled = False 

747 return 

748 

749 # if the function returned 0 (or false), or if `traces_sample_rate` is 

750 # 0, it's a sign the transaction should be dropped 

751 if not sample_rate: 

752 logger.debug( 

753 "[Tracing] Discarding {transaction_description} because {reason}".format( 

754 transaction_description=transaction_description, 

755 reason=( 

756 "traces_sampler returned 0 or False" 

757 if callable(options.get("traces_sampler")) 

758 else "traces_sample_rate is set to 0" 

759 ), 

760 ) 

761 ) 

762 self.sampled = False 

763 return 

764 

765 # Now we roll the dice. random.random is inclusive of 0, but not of 1, 

766 # so strict < is safe here. In case sample_rate is a boolean, cast it 

767 # to a float (True becomes 1.0 and False becomes 0.0) 

768 self.sampled = random.random() < float(sample_rate) 

769 

770 if self.sampled: 

771 logger.debug( 

772 "[Tracing] Starting {transaction_description}".format( 

773 transaction_description=transaction_description, 

774 ) 

775 ) 

776 else: 

777 logger.debug( 

778 "[Tracing] Discarding {transaction_description} because it's not included in the random sample (sampling rate = {sample_rate})".format( 

779 transaction_description=transaction_description, 

780 sample_rate=float(sample_rate), 

781 ) 

782 ) 

783 

784 

785# Circular imports 

786 

787from sentry_sdk.tracing_utils import ( 

788 Baggage, 

789 EnvironHeaders, 

790 compute_tracestate_entry, 

791 extract_sentrytrace_data, 

792 extract_tracestate_data, 

793 has_tracestate_enabled, 

794 has_tracing_enabled, 

795 is_valid_sample_rate, 

796 maybe_create_breadcrumbs_from_span, 

797 has_custom_measurements_enabled, 

798)