Skip to content

Commit c4ad19a

Browse files
authored
(4) Move transaction/span related functions from Hub to Scope (#2564)
Moved some functionality from Hub to Client: - moved `start_transaction` from Hub to Scope - moved `start_span` from Hub to Scope - moved `continue_trace` from Hub to Scope This is preparation work for refactoring how we deal with Hubs and Scopes in the future. Depends on: #2558 (please review the linked one first)
1 parent ac3955b commit c4ad19a

File tree

2 files changed

+167
-91
lines changed

2 files changed

+167
-91
lines changed

sentry_sdk/hub.py

Lines changed: 13 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
from sentry_sdk.consts import INSTRUMENTER
88
from sentry_sdk.scope import Scope
99
from sentry_sdk.client import Client
10-
from sentry_sdk.profiler import Profile
1110
from sentry_sdk.tracing import (
1211
NoOpSpan,
1312
Span,
1413
Transaction,
1514
)
16-
from sentry_sdk.tracing_utils import normalize_incoming_data
1715

1816
from sentry_sdk.utils import (
1917
logger,
@@ -430,54 +428,12 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
430428
431429
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
432430
"""
433-
configuration_instrumenter = self.client and self.client.options["instrumenter"]
434-
435-
if instrumenter != configuration_instrumenter:
436-
return NoOpSpan()
437-
438-
# THIS BLOCK IS DEPRECATED
439-
# TODO: consider removing this in a future release.
440-
# This is for backwards compatibility with releases before
441-
# start_transaction existed, to allow for a smoother transition.
442-
if isinstance(span, Transaction) or "transaction" in kwargs:
443-
deprecation_msg = (
444-
"Deprecated: use start_transaction to start transactions and "
445-
"Transaction.start_child to start spans."
446-
)
447-
448-
if isinstance(span, Transaction):
449-
logger.warning(deprecation_msg)
450-
return self.start_transaction(span)
451-
452-
if "transaction" in kwargs:
453-
logger.warning(deprecation_msg)
454-
name = kwargs.pop("transaction")
455-
return self.start_transaction(name=name, **kwargs)
456-
457-
# THIS BLOCK IS DEPRECATED
458-
# We do not pass a span into start_span in our code base, so I deprecate this.
459-
if span is not None:
460-
deprecation_msg = "Deprecated: passing a span into `start_span` is deprecated and will be removed in the future."
461-
logger.warning(deprecation_msg)
462-
return span
463-
464-
kwargs.setdefault("hub", self)
465-
466-
active_span = self.scope.span
467-
if active_span is not None:
468-
new_child_span = active_span.start_child(**kwargs)
469-
return new_child_span
431+
client, scope = self._stack[-1]
470432

471-
# If there is already a trace_id in the propagation context, use it.
472-
# This does not need to be done for `start_child` above because it takes
473-
# the trace_id from the parent span.
474-
if "trace_id" not in kwargs:
475-
traceparent = self.get_traceparent()
476-
trace_id = traceparent.split("-")[0] if traceparent else None
477-
if trace_id is not None:
478-
kwargs["trace_id"] = trace_id
433+
kwargs["hub"] = self
434+
kwargs["client"] = client
479435

480-
return Span(**kwargs)
436+
return scope.start_span(span=span, instrumenter=instrumenter, **kwargs)
481437

482438
def start_transaction(
483439
self, transaction=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs
@@ -507,55 +463,25 @@ def start_transaction(
507463
508464
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`.
509465
"""
510-
configuration_instrumenter = self.client and self.client.options["instrumenter"]
511-
512-
if instrumenter != configuration_instrumenter:
513-
return NoOpSpan()
514-
515-
custom_sampling_context = kwargs.pop("custom_sampling_context", {})
516-
517-
# if we haven't been given a transaction, make one
518-
if transaction is None:
519-
kwargs.setdefault("hub", self)
520-
transaction = Transaction(**kwargs)
521-
522-
# use traces_sample_rate, traces_sampler, and/or inheritance to make a
523-
# sampling decision
524-
sampling_context = {
525-
"transaction_context": transaction.to_json(),
526-
"parent_sampled": transaction.parent_sampled,
527-
}
528-
sampling_context.update(custom_sampling_context)
529-
transaction._set_initial_sampling_decision(sampling_context=sampling_context)
530-
531-
profile = Profile(transaction, hub=self)
532-
profile._set_initial_sampling_decision(sampling_context=sampling_context)
466+
client, scope = self._stack[-1]
533467

534-
# we don't bother to keep spans if we already know we're not going to
535-
# send the transaction
536-
if transaction.sampled:
537-
max_spans = (
538-
self.client and self.client.options["_experiments"].get("max_spans")
539-
) or 1000
540-
transaction.init_span_recorder(maxlen=max_spans)
468+
kwargs["hub"] = self
469+
kwargs["client"] = client
541470

542-
return transaction
471+
return scope.start_transaction(
472+
transaction=transaction, instrumenter=instrumenter, **kwargs
473+
)
543474

544475
def continue_trace(self, environ_or_headers, op=None, name=None, source=None):
545476
# type: (Dict[str, Any], Optional[str], Optional[str], Optional[str]) -> Transaction
546477
"""
547478
Sets the propagation context from environment or headers and returns a transaction.
548479
"""
549-
with self.configure_scope() as scope:
550-
scope.generate_propagation_context(environ_or_headers)
480+
scope = self._stack[-1][1]
551481

552-
transaction = Transaction.continue_from_headers(
553-
normalize_incoming_data(environ_or_headers),
554-
op=op,
555-
name=name,
556-
source=source,
482+
return scope.continue_trace(
483+
environ_or_headers=environ_or_headers, op=op, name=name, source=source
557484
)
558-
return transaction
559485

560486
@overload
561487
def push_scope(

sentry_sdk/scope.py

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
from sentry_sdk.attachments import Attachment
88
from sentry_sdk._compat import datetime_utcnow
9-
from sentry_sdk.consts import FALSE_VALUES
9+
from sentry_sdk.consts import FALSE_VALUES, INSTRUMENTER
1010
from sentry_sdk._functools import wraps
11+
from sentry_sdk.profiler import Profile
1112
from sentry_sdk.session import Session
1213
from sentry_sdk.tracing_utils import (
1314
Baggage,
@@ -18,6 +19,8 @@
1819
from sentry_sdk.tracing import (
1920
BAGGAGE_HEADER_NAME,
2021
SENTRY_TRACE_HEADER_NAME,
22+
NoOpSpan,
23+
Span,
2124
Transaction,
2225
)
2326
from sentry_sdk._types import TYPE_CHECKING
@@ -34,6 +37,7 @@
3437
from typing import Optional
3538
from typing import Tuple
3639
from typing import TypeVar
40+
from typing import Union
3741

3842
from sentry_sdk._types import (
3943
Breadcrumb,
@@ -46,9 +50,6 @@
4650
Type,
4751
)
4852

49-
from sentry_sdk.profiler import Profile
50-
from sentry_sdk.tracing import Span
51-
5253
F = TypeVar("F", bound=Callable[..., Any])
5354
T = TypeVar("T")
5455

@@ -636,6 +637,155 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
636637
while len(self._breadcrumbs) > max_breadcrumbs:
637638
self._breadcrumbs.popleft()
638639

640+
def start_transaction(
641+
self, transaction=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs
642+
):
643+
# type: (Optional[Transaction], str, Any) -> Union[Transaction, NoOpSpan]
644+
"""
645+
Start and return a transaction.
646+
647+
Start an existing transaction if given, otherwise create and start a new
648+
transaction with kwargs.
649+
650+
This is the entry point to manual tracing instrumentation.
651+
652+
A tree structure can be built by adding child spans to the transaction,
653+
and child spans to other spans. To start a new child span within the
654+
transaction or any span, call the respective `.start_child()` method.
655+
656+
Every child span must be finished before the transaction is finished,
657+
otherwise the unfinished spans are discarded.
658+
659+
When used as context managers, spans and transactions are automatically
660+
finished at the end of the `with` block. If not using context managers,
661+
call the `.finish()` method.
662+
663+
When the transaction is finished, it will be sent to Sentry with all its
664+
finished child spans.
665+
666+
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`.
667+
"""
668+
hub = kwargs.pop("hub", None)
669+
client = kwargs.pop("client", None)
670+
671+
configuration_instrumenter = client and client.options["instrumenter"]
672+
673+
if instrumenter != configuration_instrumenter:
674+
return NoOpSpan()
675+
676+
custom_sampling_context = kwargs.pop("custom_sampling_context", {})
677+
678+
# if we haven't been given a transaction, make one
679+
if transaction is None:
680+
kwargs.setdefault("hub", hub)
681+
transaction = Transaction(**kwargs)
682+
683+
# use traces_sample_rate, traces_sampler, and/or inheritance to make a
684+
# sampling decision
685+
sampling_context = {
686+
"transaction_context": transaction.to_json(),
687+
"parent_sampled": transaction.parent_sampled,
688+
}
689+
sampling_context.update(custom_sampling_context)
690+
transaction._set_initial_sampling_decision(sampling_context=sampling_context)
691+
692+
profile = Profile(transaction, hub=hub)
693+
profile._set_initial_sampling_decision(sampling_context=sampling_context)
694+
695+
# we don't bother to keep spans if we already know we're not going to
696+
# send the transaction
697+
if transaction.sampled:
698+
max_spans = (
699+
client and client.options["_experiments"].get("max_spans")
700+
) or 1000
701+
transaction.init_span_recorder(maxlen=max_spans)
702+
703+
return transaction
704+
705+
def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
706+
# type: (Optional[Span], str, Any) -> Span
707+
"""
708+
Start a span whose parent is the currently active span or transaction, if any.
709+
710+
The return value is a :py:class:`sentry_sdk.tracing.Span` instance,
711+
typically used as a context manager to start and stop timing in a `with`
712+
block.
713+
714+
Only spans contained in a transaction are sent to Sentry. Most
715+
integrations start a transaction at the appropriate time, for example
716+
for every incoming HTTP request. Use
717+
:py:meth:`sentry_sdk.start_transaction` to start a new transaction when
718+
one is not already in progress.
719+
720+
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
721+
"""
722+
client = kwargs.get("client", None)
723+
724+
configuration_instrumenter = client and client.options["instrumenter"]
725+
726+
if instrumenter != configuration_instrumenter:
727+
return NoOpSpan()
728+
729+
# THIS BLOCK IS DEPRECATED
730+
# TODO: consider removing this in a future release.
731+
# This is for backwards compatibility with releases before
732+
# start_transaction existed, to allow for a smoother transition.
733+
if isinstance(span, Transaction) or "transaction" in kwargs:
734+
deprecation_msg = (
735+
"Deprecated: use start_transaction to start transactions and "
736+
"Transaction.start_child to start spans."
737+
)
738+
739+
if isinstance(span, Transaction):
740+
logger.warning(deprecation_msg)
741+
return self.start_transaction(span, **kwargs)
742+
743+
if "transaction" in kwargs:
744+
logger.warning(deprecation_msg)
745+
name = kwargs.pop("transaction")
746+
return self.start_transaction(name=name, **kwargs)
747+
748+
# THIS BLOCK IS DEPRECATED
749+
# We do not pass a span into start_span in our code base, so I deprecate this.
750+
if span is not None:
751+
deprecation_msg = "Deprecated: passing a span into `start_span` is deprecated and will be removed in the future."
752+
logger.warning(deprecation_msg)
753+
return span
754+
755+
kwargs.pop("client")
756+
757+
active_span = self.span
758+
if active_span is not None:
759+
new_child_span = active_span.start_child(**kwargs)
760+
return new_child_span
761+
762+
# If there is already a trace_id in the propagation context, use it.
763+
# This does not need to be done for `start_child` above because it takes
764+
# the trace_id from the parent span.
765+
if "trace_id" not in kwargs:
766+
traceparent = self.get_traceparent()
767+
trace_id = traceparent.split("-")[0] if traceparent else None
768+
if trace_id is not None:
769+
kwargs["trace_id"] = trace_id
770+
771+
return Span(**kwargs)
772+
773+
def continue_trace(self, environ_or_headers, op=None, name=None, source=None):
774+
# type: (Dict[str, Any], Optional[str], Optional[str], Optional[str]) -> Transaction
775+
"""
776+
Sets the propagation context from environment or headers and returns a transaction.
777+
"""
778+
self.generate_propagation_context(environ_or_headers)
779+
780+
transaction = Transaction.continue_from_headers(
781+
normalize_incoming_data(environ_or_headers),
782+
op=op,
783+
name=name,
784+
source=source,
785+
)
786+
787+
return transaction
788+
639789
def start_session(self, *args, **kwargs):
640790
# type: (*Any, **Any) -> None
641791
"""Starts a new session."""

0 commit comments

Comments
 (0)