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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import uuid
2import random
3import time
5from datetime import datetime, timedelta
7import sentry_sdk
9from sentry_sdk.utils import logger
10from sentry_sdk._types import MYPY
13if MYPY: 13 ↛ 14line 13 didn't jump to line 14, because the condition on line 13 was never true
14 import typing
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
23 from sentry_sdk._types import SamplingContext, MeasurementUnit
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"
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}
49class _SpanRecorder(object):
50 """Limits the number of spans recorded in a transaction."""
52 __slots__ = ("maxlen", "spans")
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]
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)
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 )
93 def __new__(cls, **kwargs):
94 # type: (**Any) -> Any
95 """
96 Backwards-compatible implementation of Span and Transaction
97 creation.
98 """
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)
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()
139 # Python 3.3+
140 self._start_timestamp_monotonic = time.perf_counter()
141 except AttributeError:
142 pass
144 #: End timestamp of span
145 self.timestamp = None # type: Optional[datetime]
147 self._span_recorder = None # type: Optional[_SpanRecorder]
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)
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 )
171 def __enter__(self):
172 # type: () -> Span
173 hub = self.hub or sentry_sdk.Hub.current
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
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")
186 hub, scope, old_span = self._context_manager_state
187 del self._context_manager_state
189 self.finish(hub)
190 scope.span = old_span
192 @property
193 def containing_transaction(self):
194 # type: () -> Optional[Transaction]
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
201 def start_child(self, **kwargs):
202 # type: (**Any) -> Span
203 """
204 Start a sub-span from the current span or transaction.
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)
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 )
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
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)
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.
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)
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 )
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})
278 sentrytrace_kwargs = extract_sentrytrace_data(headers.get("sentry-trace"))
280 if sentrytrace_kwargs is not None:
281 kwargs.update(sentrytrace_kwargs)
282 baggage.freeze
284 kwargs.update(extract_tracestate_data(headers.get("tracestate")))
286 transaction = Transaction(**kwargs)
287 transaction.same_process_as_parent = False
289 return transaction
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.
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()
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
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()
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)
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.
326 """
327 logger.warning(
328 "Deprecated: Use Transaction.continue_from_headers(headers, **kwargs) "
329 "instead of from_traceparent(traceparent, **kwargs)"
330 )
332 if not traceparent:
333 return None
335 return cls.continue_from_headers({"sentry-trace": traceparent}, **kwargs)
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)
346 def to_tracestate(self):
347 # type: () -> Optional[str]
348 """
349 Computes the `tracestate` header value using data from the containing
350 transaction.
352 If the containing transaction doesn't yet have a `sentry_tracestate`
353 value, this will cause one to be generated and stored.
355 If there is no containing transaction, a value will be generated but not
356 stored.
358 Returns None if there's no client and/or no DSN.
359 """
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 )
368 if not sentry_tracestate:
369 return None
371 header_value = sentry_tracestate
373 if third_party_tracestate:
374 header_value = header_value + "," + third_party_tracestate
376 return header_value
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.
383 If the transaction doesn't yet have a `_sentry_tracestate` value,
384 compute one and store it.
385 """
386 transaction = self.containing_transaction
388 if transaction:
389 if not transaction._sentry_tracestate:
390 transaction._sentry_tracestate = compute_tracestate_entry(self)
392 return transaction._sentry_tracestate
394 # orphan span - nowhere to store the value, so just return it
395 return compute_tracestate_entry(self)
397 def set_tag(self, key, value):
398 # type: (str, Any) -> None
399 self._tags[key] = value
401 def set_data(self, key, value):
402 # type: (str, Any) -> None
403 self._data[key] = value
405 def set_status(self, value):
406 # type: (str) -> None
407 self.status = value
409 def set_http_status(self, http_status):
410 # type: (int) -> None
411 self.set_tag("http.status_code", str(http_status))
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")
442 def is_success(self):
443 # type: () -> bool
444 return self.status == "ok"
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
454 hub = hub or self.hub or sentry_sdk.Hub.current
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()
462 maybe_create_breadcrumbs_from_span(hub, self)
463 return None
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]
478 if self.status:
479 self._tags["status"] = self.status
481 tags = self._tags
482 if tags:
483 rv["tags"] = tags
485 data = self._data
486 if data:
487 rv["data"] = data
489 return rv
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
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()
509 if sentry_tracestate:
510 rv["tracestate"] = sentry_tracestate
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()
518 return rv
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 )
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
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 )
585 @property
586 def containing_transaction(self):
587 # type: () -> Transaction
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
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
600 hub = hub or self.hub or sentry_sdk.Hub.current
601 client = hub.client
603 if client is None:
604 # We have no client and therefore nowhere to send this transaction.
605 return None
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")
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 )
619 return None
621 if not self.name:
622 logger.warning(
623 "Transaction has no name, falling back to `<unlabeled transaction>`."
624 )
625 self.name = "<unlabeled transaction>"
627 Span.finish(self, hub)
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
636 finished_spans = [
637 span.to_json()
638 for span in self._span_recorder.spans
639 if span.timestamp is not None
640 ]
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
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 }
659 if has_custom_measurements_enabled():
660 event["measurements"] = self._measurements
662 return hub.capture_event(event)
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
672 self._measurements[name] = {"value": value, "unit": unit}
674 def to_json(self):
675 # type: () -> Dict[str, Any]
676 rv = super(Transaction, self).to_json()
678 rv["name"] = self.name
679 rv["source"] = self.source
680 rv["sampled"] = self.sampled
682 return rv
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:
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
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.
699 3. If `traces_sampler` is not defined, but there's a parent sampling
700 decision, the parent sampling decision will be used.
702 4. If `traces_sampler` is not defined and there's no parent sampling
703 decision, `traces_sample_rate` will be used.
704 """
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 )
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
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
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 )
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
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
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)
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 )
785# Circular imports
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)