66from datetime import datetime , timedelta
77
88import sentry_sdk
9+ from sentry_sdk .consts import INSTRUMENTER
910from sentry_sdk .utils import logger
1011from sentry_sdk ._types import MYPY
1112
@@ -125,6 +126,7 @@ def __init__(
125126 status = None , # type: Optional[str]
126127 transaction = None , # type: Optional[str] # deprecated
127128 containing_transaction = None , # type: Optional[Transaction]
129+ start_timestamp = None , # type: Optional[datetime]
128130 ):
129131 # type: (...) -> None
130132 self .trace_id = trace_id or uuid .uuid4 ().hex
@@ -139,7 +141,7 @@ def __init__(
139141 self ._tags = {} # type: Dict[str, str]
140142 self ._data = {} # type: Dict[str, Any]
141143 self ._containing_transaction = containing_transaction
142- self .start_timestamp = datetime .utcnow ()
144+ self .start_timestamp = start_timestamp or datetime .utcnow ()
143145 try :
144146 # TODO: For Python 3.7+, we could use a clock with ns resolution:
145147 # self._start_timestamp_monotonic = time.perf_counter_ns()
@@ -206,15 +208,22 @@ def containing_transaction(self):
206208 # referencing themselves)
207209 return self ._containing_transaction
208210
209- def start_child (self , ** kwargs ):
210- # type: (**Any) -> Span
211+ def start_child (self , instrumenter = INSTRUMENTER . SENTRY , ** kwargs ):
212+ # type: (str, **Any) -> Span
211213 """
212214 Start a sub-span from the current span or transaction.
213215
214216 Takes the same arguments as the initializer of :py:class:`Span`. The
215217 trace id, sampling decision, transaction pointer, and span recorder are
216218 inherited from the current span/transaction.
217219 """
220+ hub = self .hub or sentry_sdk .Hub .current
221+ client = hub .client
222+ configuration_instrumenter = client and client .options ["instrumenter" ]
223+
224+ if instrumenter != configuration_instrumenter :
225+ return NoOpSpan ()
226+
218227 kwargs .setdefault ("sampled" , self .sampled )
219228
220229 child = Span (
@@ -461,8 +470,8 @@ def is_success(self):
461470 # type: () -> bool
462471 return self .status == "ok"
463472
464- def finish (self , hub = None ):
465- # type: (Optional[sentry_sdk.Hub]) -> Optional[str]
473+ def finish (self , hub = None , end_timestamp = None ):
474+ # type: (Optional[sentry_sdk.Hub], Optional[datetime] ) -> Optional[str]
466475 # XXX: would be type: (Optional[sentry_sdk.Hub]) -> None, but that leads
467476 # to incompatible return types for Span.finish and Transaction.finish.
468477 if self .timestamp is not None :
@@ -472,8 +481,13 @@ def finish(self, hub=None):
472481 hub = hub or self .hub or sentry_sdk .Hub .current
473482
474483 try :
475- duration_seconds = time .perf_counter () - self ._start_timestamp_monotonic
476- self .timestamp = self .start_timestamp + timedelta (seconds = duration_seconds )
484+ if end_timestamp :
485+ self .timestamp = end_timestamp
486+ else :
487+ duration_seconds = time .perf_counter () - self ._start_timestamp_monotonic
488+ self .timestamp = self .start_timestamp + timedelta (
489+ seconds = duration_seconds
490+ )
477491 except AttributeError :
478492 self .timestamp = datetime .utcnow ()
479493
@@ -550,6 +564,7 @@ class Transaction(Span):
550564 # tracestate data from other vendors, of the form `dogs=yes,cats=maybe`
551565 "_third_party_tracestate" ,
552566 "_measurements" ,
567+ "_contexts" ,
553568 "_profile" ,
554569 "_baggage" ,
555570 "_active_thread_id" ,
@@ -575,7 +590,9 @@ def __init__(
575590 "instead of Span(transaction=...)."
576591 )
577592 name = kwargs .pop ("transaction" )
593+
578594 Span .__init__ (self , ** kwargs )
595+
579596 self .name = name
580597 self .source = source
581598 self .sample_rate = None # type: Optional[float]
@@ -586,6 +603,7 @@ def __init__(
586603 self ._sentry_tracestate = sentry_tracestate
587604 self ._third_party_tracestate = third_party_tracestate
588605 self ._measurements = {} # type: Dict[str, Any]
606+ self ._contexts = {} # type: Dict[str, Any]
589607 self ._profile = None # type: Optional[sentry_sdk.profiler.Profile]
590608 self ._baggage = baggage
591609 # for profiling, we want to know on which thread a transaction is started
@@ -619,8 +637,8 @@ def containing_transaction(self):
619637 # reference.
620638 return self
621639
622- def finish (self , hub = None ):
623- # type: (Optional[sentry_sdk.Hub]) -> Optional[str]
640+ def finish (self , hub = None , end_timestamp = None ):
641+ # type: (Optional[sentry_sdk.Hub], Optional[datetime] ) -> Optional[str]
624642 if self .timestamp is not None :
625643 # This transaction is already finished, ignore.
626644 return None
@@ -652,7 +670,7 @@ def finish(self, hub=None):
652670 )
653671 self .name = "<unlabeled transaction>"
654672
655- Span .finish (self , hub )
673+ Span .finish (self , hub , end_timestamp )
656674
657675 if not self .sampled :
658676 # At this point a `sampled = None` should have already been resolved
@@ -674,11 +692,15 @@ def finish(self, hub=None):
674692 # to be garbage collected
675693 self ._span_recorder = None
676694
695+ contexts = {}
696+ contexts .update (self ._contexts )
697+ contexts .update ({"trace" : self .get_trace_context ()})
698+
677699 event = {
678700 "type" : "transaction" ,
679701 "transaction" : self .name ,
680702 "transaction_info" : {"source" : self .source },
681- "contexts" : { "trace" : self . get_trace_context ()} ,
703+ "contexts" : contexts ,
682704 "tags" : self ._tags ,
683705 "timestamp" : self .timestamp ,
684706 "start_timestamp" : self .start_timestamp ,
@@ -703,6 +725,10 @@ def set_measurement(self, name, value, unit=""):
703725
704726 self ._measurements [name ] = {"value" : value , "unit" : unit }
705727
728+ def set_context (self , key , value ):
729+ # type: (str, Any) -> None
730+ self ._contexts [key ] = value
731+
706732 def to_json (self ):
707733 # type: () -> Dict[str, Any]
708734 rv = super (Transaction , self ).to_json ()
@@ -828,6 +854,48 @@ def _set_initial_sampling_decision(self, sampling_context):
828854 )
829855
830856
857+ class NoOpSpan (Span ):
858+ def __repr__ (self ):
859+ # type: () -> Any
860+ return self .__class__ .__name__
861+
862+ def __enter__ (self ):
863+ # type: () -> Any
864+ return self
865+
866+ def __exit__ (self , ty , value , tb ):
867+ # type: (Any, Any, Any) -> Any
868+ pass
869+
870+ def start_child (self , instrumenter = INSTRUMENTER .SENTRY , ** kwargs ):
871+ # type: (str, **Any) -> Any
872+ pass
873+
874+ def new_span (self , ** kwargs ):
875+ # type: (**Any) -> Any
876+ pass
877+
878+ def set_tag (self , key , value ):
879+ # type: (Any, Any) -> Any
880+ pass
881+
882+ def set_data (self , key , value ):
883+ # type: (Any, Any) -> Any
884+ pass
885+
886+ def set_status (self , value ):
887+ # type: (Any) -> Any
888+ pass
889+
890+ def set_http_status (self , http_status ):
891+ # type: (Any) -> Any
892+ pass
893+
894+ def finish (self , hub = None , end_timestamp = None ):
895+ # type: (Any, Any) -> Any
896+ pass
897+
898+
831899# Circular imports
832900
833901from sentry_sdk .tracing_utils import (
0 commit comments