11from dataclasses import dataclass
2- from functools import partial
32from itertools import count
43import sys
54from threading import current_thread
1716from ddtrace .debugging ._probe .model import LogLineProbe
1817from ddtrace .debugging ._probe .model import ProbeEvalTiming
1918from ddtrace .debugging ._session import Session
20- from ddtrace .debugging ._signal .collector import SignalCollector
2119from ddtrace .debugging ._signal .snapshot import Snapshot
2220from ddtrace .debugging ._uploader import SignalUploader
2321from ddtrace .debugging ._uploader import UploaderProduct
2422from ddtrace .ext import EXIT_SPAN_TYPES
25- from ddtrace .internal import core
2623from ddtrace .internal .compat import Path
24+ from ddtrace .internal .forksafe import Lock
25+ from ddtrace .internal .logger import get_logger
2726from ddtrace .internal .packages import is_user_code
2827from ddtrace .internal .safety import _isinstance
2928from ddtrace .internal .wrapping .context import WrappingContext
3029from ddtrace .settings .code_origin import config as co_config
3130from ddtrace .trace import Span
3231
3332
33+ log = get_logger (__name__ )
34+
35+
3436def frame_stack (frame : FrameType ) -> t .Iterator [FrameType ]:
3537 _frame : t .Optional [FrameType ] = frame
3638 while _frame is not None :
3739 yield _frame
3840 _frame = _frame .f_back
3941
4042
41- def wrap_entrypoint (collector : SignalCollector , f : t .Callable ) -> None :
42- if not _isinstance (f , FunctionType ):
43- return
44-
45- _f = t .cast (FunctionType , f )
46- if not EntrySpanWrappingContext .is_wrapped (_f ):
47- EntrySpanWrappingContext (collector , _f ).wrap ()
48-
49-
5043@dataclass
5144class EntrySpanProbe (LogFunctionProbe ):
5245 __span_class__ = "entry"
@@ -118,12 +111,13 @@ class EntrySpanLocation:
118111
119112
120113class EntrySpanWrappingContext (WrappingContext ):
114+ __enabled__ = False
121115 __priority__ = 199
122116
123- def __init__ (self , collector : SignalCollector , f : FunctionType ) -> None :
117+ def __init__ (self , uploader : t . Type [ SignalUploader ] , f : FunctionType ) -> None :
124118 super ().__init__ (f )
125119
126- self .collector = collector
120+ self .uploader = uploader
127121
128122 filename = str (Path (f .__code__ .co_filename ).resolve ())
129123 name = f .__qualname__
@@ -139,26 +133,30 @@ def __init__(self, collector: SignalCollector, f: FunctionType) -> None:
139133 def __enter__ (self ):
140134 super ().__enter__ ()
141135
142- root = ddtrace .tracer .current_root_span ()
143- span = ddtrace .tracer .current_span ()
144- location = self .location
145- if root is None or span is None or root .get_tag ("_dd.entry_location.file" ) is not None :
146- return self
136+ if self .__enabled__ :
137+ root = ddtrace .tracer .current_root_span ()
138+ span = ddtrace .tracer .current_span ()
139+ location = self .location
140+ if root is None or span is None or root .get_tag ("_dd.entry_location.file" ) is not None :
141+ return self
147142
148- # Add tags to the local root
149- for s in (root , span ):
150- s ._set_tag_str ("_dd.code_origin.type" , "entry" )
143+ # Add tags to the local root
144+ for s in (root , span ):
145+ s ._set_tag_str ("_dd.code_origin.type" , "entry" )
151146
152- s ._set_tag_str ("_dd.code_origin.frames.0.file" , location .file )
153- s ._set_tag_str ("_dd.code_origin.frames.0.line" , str (location .line ))
154- s ._set_tag_str ("_dd.code_origin.frames.0.type" , location .module )
155- s ._set_tag_str ("_dd.code_origin.frames.0.method" , location .name )
147+ s ._set_tag_str ("_dd.code_origin.frames.0.file" , location .file )
148+ s ._set_tag_str ("_dd.code_origin.frames.0.line" , str (location .line ))
149+ s ._set_tag_str ("_dd.code_origin.frames.0.type" , location .module )
150+ s ._set_tag_str ("_dd.code_origin.frames.0.method" , location .name )
156151
157- self .set ("start_time" , monotonic_ns ())
152+ self .set ("start_time" , monotonic_ns ())
158153
159154 return self
160155
161156 def _close_signal (self , retval = None , exc_info = (None , None , None )):
157+ if not self .__enabled__ :
158+ return
159+
162160 root = ddtrace .tracer .current_root_span ()
163161 span = ddtrace .tracer .current_span ()
164162 if root is None or span is None :
@@ -167,6 +165,12 @@ def _close_signal(self, retval=None, exc_info=(None, None, None)):
167165 # Check if we have any level 2 debugging sessions running for the
168166 # current trace
169167 if any (s .level >= 2 for s in Session .from_trace (root .context or span .context )):
168+ try :
169+ start_time : int = self .get ("start_time" )
170+ except KeyError :
171+ # Context was not opened
172+ return
173+
170174 # Create a snapshot
171175 snapshot = Snapshot (
172176 probe = self .location .probe ,
@@ -182,9 +186,10 @@ def _close_signal(self, retval=None, exc_info=(None, None, None)):
182186 root ._set_tag_str ("_dd.code_origin.frames.0.snapshot_id" , snapshot .uuid )
183187 span ._set_tag_str ("_dd.code_origin.frames.0.snapshot_id" , snapshot .uuid )
184188
185- snapshot .do_exit (retval , exc_info , monotonic_ns () - self . get ( " start_time" ) )
189+ snapshot .do_exit (retval , exc_info , monotonic_ns () - start_time )
186190
187- self .collector .push (snapshot )
191+ if (collector := self .uploader .get_collector ()) is not None :
192+ collector .push (snapshot )
188193
189194 def __return__ (self , retval ):
190195 self ._close_signal (retval = retval )
@@ -195,42 +200,68 @@ def __exit__(self, exc_type, exc_value, traceback):
195200 super ().__exit__ (exc_type , exc_value , traceback )
196201
197202
198- @dataclass
199203class SpanCodeOriginProcessorEntry :
200204 __uploader__ = SignalUploader
205+ __context_wrapper__ = EntrySpanWrappingContext
201206
202207 _instance : t .Optional ["SpanCodeOriginProcessorEntry" ] = None
203- _handler : t .Optional [t .Callable ] = None
208+
209+ _pending : t .List = []
210+ _lock = Lock ()
211+
212+ @classmethod
213+ def instrument_view (cls , f ):
214+ if not _isinstance (f , FunctionType ):
215+ return
216+
217+ with cls ._lock :
218+ if cls ._instance is None :
219+ # Entry span code origin is not enabled, so we defer the
220+ # instrumentation
221+ cls ._pending .append (f )
222+ return
223+
224+ _f = t .cast (FunctionType , f )
225+ if not EntrySpanWrappingContext .is_wrapped (_f ):
226+ log .debug ("Patching entrypoint %r for code origin" , f )
227+ EntrySpanWrappingContext (cls .__uploader__ , _f ).wrap ()
204228
205229 @classmethod
206230 def enable (cls ):
207231 if cls ._instance is not None :
208232 return
209233
210- cls ._instance = cls ()
234+ with cls ._lock :
235+ cls ._instance = cls ()
236+
237+ # Instrument the pending views
238+ while cls ._pending :
239+ cls .instrument_view (cls ._pending .pop ())
211240
212241 # Register code origin for span with the snapshot uploader
213- cls .__uploader__ .register (UploaderProduct .CODE_ORIGIN_SPAN )
242+ cls .__uploader__ .register (UploaderProduct .CODE_ORIGIN_SPAN_ENTRY )
214243
215- # Register the entrypoint wrapping for entry spans
216- cls ._handler = handler = partial (wrap_entrypoint , cls .__uploader__ .get_collector ())
217- core .on ("service_entrypoint.patch" , handler )
244+ # Enable the context wrapper
245+ cls .__context_wrapper__ .__enabled__ = True
246+
247+ log .debug ("Code Origin for Spans (entry) enabled" )
218248
219249 @classmethod
220250 def disable (cls ):
221251 if cls ._instance is None :
222252 return
223253
224- # Unregister the entrypoint wrapping for entry spans
225- core .reset_listeners ("service_entrypoint.patch" , cls ._handler )
254+ # Disable the context wrapper
255+ cls .__context_wrapper__ .__enabled__ = False
256+
226257 # Unregister code origin for span with the snapshot uploader
227- cls .__uploader__ .unregister (UploaderProduct .CODE_ORIGIN_SPAN )
258+ cls .__uploader__ .unregister (UploaderProduct .CODE_ORIGIN_SPAN_ENTRY )
228259
229- cls ._handler = None
230260 cls ._instance = None
231261
262+ log .debug ("Code Origin for Spans (entry) disabled" )
263+
232264
233- @dataclass
234265class SpanCodeOriginProcessorExit (SpanProcessor ):
235266 __uploader__ = SignalUploader
236267
@@ -283,7 +314,8 @@ def on_span_start(self, span: Span) -> None:
283314 snapshot .do_line ()
284315
285316 # Collect
286- self .__uploader__ .get_collector ().push (snapshot )
317+ if (collector := self .__uploader__ .get_collector ()) is not None :
318+ collector .push (snapshot )
287319
288320 # Correlate the snapshot with the span
289321 span ._set_tag_str (f"_dd.code_origin.frames.{ n } .snapshot_id" , snapshot .uuid )
@@ -299,11 +331,13 @@ def enable(cls):
299331 instance = cls ._instance = cls ()
300332
301333 # Register code origin for span with the snapshot uploader
302- cls .__uploader__ .register (UploaderProduct .CODE_ORIGIN_SPAN )
334+ cls .__uploader__ .register (UploaderProduct .CODE_ORIGIN_SPAN_EXIT )
303335
304336 # Register the processor for exit spans
305337 instance .register ()
306338
339+ log .debug ("Code Origin for Spans (exit) enabled" )
340+
307341 @classmethod
308342 def disable (cls ):
309343 if cls ._instance is None :
@@ -313,6 +347,8 @@ def disable(cls):
313347 cls ._instance .unregister ()
314348
315349 # Unregister code origin for span with the snapshot uploader
316- cls .__uploader__ .unregister (UploaderProduct .CODE_ORIGIN_SPAN )
350+ cls .__uploader__ .unregister (UploaderProduct .CODE_ORIGIN_SPAN_EXIT )
317351
318352 cls ._instance = None
353+
354+ log .debug ("Code Origin for Spans (exit) disabled" )
0 commit comments