|
2 | 2 | import uuid
|
3 | 3 | import contextlib
|
4 | 4 | import math
|
| 5 | +import random |
5 | 6 | import time
|
6 | 7 |
|
7 | 8 | from datetime import datetime, timedelta
|
8 | 9 | from numbers import Real
|
9 | 10 |
|
10 | 11 | import sentry_sdk
|
11 | 12 |
|
12 |
| -from sentry_sdk.utils import capture_internal_exceptions, logger, to_string |
| 13 | +from sentry_sdk.utils import ( |
| 14 | + capture_internal_exceptions, |
| 15 | + logger, |
| 16 | + to_string, |
| 17 | +) |
13 | 18 | from sentry_sdk._compat import PY2
|
14 | 19 | from sentry_sdk._types import MYPY
|
15 | 20 |
|
|
28 | 33 | from typing import List
|
29 | 34 | from typing import Tuple
|
30 | 35 |
|
| 36 | + from sentry_sdk._types import SamplingContext |
| 37 | + |
31 | 38 | _traceparent_header_format_re = re.compile(
|
32 | 39 | "^[ \t]*" # whitespace
|
33 | 40 | "([0-9a-f]{32})?" # trace_id
|
@@ -337,7 +344,7 @@ def from_traceparent(
|
337 | 344 | return Transaction(
|
338 | 345 | trace_id=trace_id,
|
339 | 346 | parent_span_id=parent_span_id,
|
340 |
| - sampled=parent_sampled, |
| 347 | + parent_sampled=parent_sampled, |
341 | 348 | **kwargs
|
342 | 349 | )
|
343 | 350 |
|
@@ -555,6 +562,116 @@ def to_json(self):
|
555 | 562 |
|
556 | 563 | return rv
|
557 | 564 |
|
| 565 | + def _set_initial_sampling_decision(self, sampling_context): |
| 566 | + # type: (SamplingContext) -> None |
| 567 | + """ |
| 568 | + Sets the transaction's sampling decision, according to the following |
| 569 | + precedence rules: |
| 570 | +
|
| 571 | + 1. If a sampling decision is passed to `start_transaction` |
| 572 | + (`start_transaction(name: "my transaction", sampled: True)`), that |
| 573 | + decision will be used, regardlesss of anything else |
| 574 | +
|
| 575 | + 2. If `traces_sampler` is defined, its decision will be used. It can |
| 576 | + choose to keep or ignore any parent sampling decision, or use the |
| 577 | + sampling context data to make its own decision or to choose a sample |
| 578 | + rate for the transaction. |
| 579 | +
|
| 580 | + 3. If `traces_sampler` is not defined, but there's a parent sampling |
| 581 | + decision, the parent sampling decision will be used. |
| 582 | +
|
| 583 | + 4. If `traces_sampler` is not defined and there's no parent sampling |
| 584 | + decision, `traces_sample_rate` will be used. |
| 585 | + """ |
| 586 | + |
| 587 | + hub = self.hub or sentry_sdk.Hub.current |
| 588 | + client = hub.client |
| 589 | + options = (client and client.options) or {} |
| 590 | + transaction_description = "{op}transaction <{name}>".format( |
| 591 | + op=("<" + self.op + "> " if self.op else ""), name=self.name |
| 592 | + ) |
| 593 | + |
| 594 | + # nothing to do if there's no client or if tracing is disabled |
| 595 | + if not client or not has_tracing_enabled(options): |
| 596 | + self.sampled = False |
| 597 | + return |
| 598 | + |
| 599 | + # if the user has forced a sampling decision by passing a `sampled` |
| 600 | + # value when starting the transaction, go with that |
| 601 | + if self.sampled is not None: |
| 602 | + return |
| 603 | + |
| 604 | + # we would have bailed already if neither `traces_sampler` nor |
| 605 | + # `traces_sample_rate` were defined, so one of these should work; prefer |
| 606 | + # the hook if so |
| 607 | + sample_rate = ( |
| 608 | + options["traces_sampler"](sampling_context) |
| 609 | + if callable(options.get("traces_sampler")) |
| 610 | + else ( |
| 611 | + # default inheritance behavior |
| 612 | + sampling_context["parent_sampled"] |
| 613 | + if sampling_context["parent_sampled"] is not None |
| 614 | + else options["traces_sample_rate"] |
| 615 | + ) |
| 616 | + ) |
| 617 | + |
| 618 | + # Since this is coming from the user (or from a function provided by the |
| 619 | + # user), who knows what we might get. (The only valid values are |
| 620 | + # booleans or numbers between 0 and 1.) |
| 621 | + if not _is_valid_sample_rate(sample_rate): |
| 622 | + logger.warning( |
| 623 | + "[Tracing] Discarding {transaction_description} because of invalid sample rate.".format( |
| 624 | + transaction_description=transaction_description, |
| 625 | + ) |
| 626 | + ) |
| 627 | + self.sampled = False |
| 628 | + return |
| 629 | + |
| 630 | + # if the function returned 0 (or false), or if `traces_sample_rate` is |
| 631 | + # 0, it's a sign the transaction should be dropped |
| 632 | + if not sample_rate: |
| 633 | + logger.debug( |
| 634 | + "[Tracing] Discarding {transaction_description} because {reason}".format( |
| 635 | + transaction_description=transaction_description, |
| 636 | + reason=( |
| 637 | + "traces_sampler returned 0 or False" |
| 638 | + if callable(options.get("traces_sampler")) |
| 639 | + else "traces_sample_rate is set to 0" |
| 640 | + ), |
| 641 | + ) |
| 642 | + ) |
| 643 | + self.sampled = False |
| 644 | + return |
| 645 | + |
| 646 | + # Now we roll the dice. random.random is inclusive of 0, but not of 1, |
| 647 | + # so strict < is safe here. In case sample_rate is a boolean, cast it |
| 648 | + # to a float (True becomes 1.0 and False becomes 0.0) |
| 649 | + self.sampled = random.random() < float(sample_rate) |
| 650 | + |
| 651 | + if self.sampled: |
| 652 | + logger.debug( |
| 653 | + "[Tracing] Starting {transaction_description}".format( |
| 654 | + transaction_description=transaction_description, |
| 655 | + ) |
| 656 | + ) |
| 657 | + else: |
| 658 | + logger.debug( |
| 659 | + "[Tracing] Discarding {transaction_description} because it's not included in the random sample (sampling rate = {sample_rate})".format( |
| 660 | + transaction_description=transaction_description, |
| 661 | + sample_rate=float(sample_rate), |
| 662 | + ) |
| 663 | + ) |
| 664 | + |
| 665 | + |
| 666 | +def has_tracing_enabled(options): |
| 667 | + # type: (Dict[str, Any]) -> bool |
| 668 | + """ |
| 669 | + Returns True if either traces_sample_rate or traces_sampler is |
| 670 | + non-zero/defined, False otherwise. |
| 671 | + """ |
| 672 | + |
| 673 | + return bool(options.get("traces_sample_rate") or options.get("traces_sampler")) |
| 674 | + |
558 | 675 |
|
559 | 676 | def _is_valid_sample_rate(rate):
|
560 | 677 | # type: (Any) -> bool
|
|
0 commit comments