11"""Tracing manager for handling tracer implementations and function registry."""
22
33import logging
4- from typing import Any , Callable , List , Optional
4+ from typing import Any , Callable , Dict , List , Optional
55
66from opentelemetry import context , trace
77from opentelemetry .sdk .trace import ReadableSpan
1010logger = logging .getLogger (__name__ )
1111
1212
13+ class SpanRegistry :
14+ """Registry to track all spans and their parent relationships."""
15+
16+ def __init__ (self ):
17+ self ._spans : Dict [int , ReadableSpan ] = {} # span_id -> span
18+ self ._parent_map : Dict [int , Optional [int ]] = {} # span_id -> parent_id
19+
20+ def register_span (self , span : ReadableSpan ) -> None :
21+ """Register a span and its parent relationship."""
22+ span_id = span .get_span_context ().span_id
23+ parent_id = span .parent .span_id if span .parent else None
24+
25+ self ._spans [span_id ] = span
26+ self ._parent_map [span_id ] = parent_id
27+
28+ parent_str = f"{ parent_id :016x} " if parent_id is not None else "None"
29+ logger .info (f"Registered span: { span .name } (id: { span_id :016x} , parent: { parent_str } )" )
30+
31+ def get_span (self , span_id : int ) -> Optional [ReadableSpan ]:
32+ """Get a span by ID."""
33+ return self ._spans .get (span_id )
34+
35+ def get_parent_id (self , span_id : int ) -> Optional [int ]:
36+ """Get the parent ID of a span."""
37+ return self ._parent_map .get (span_id )
38+
39+ def calculate_depth (self , span_id : int ) -> int :
40+ """Calculate the depth of a span in the hierarchy."""
41+ depth = 0
42+ current_id = span_id
43+ visited = set ()
44+
45+ while current_id is not None and current_id not in visited :
46+ visited .add (current_id )
47+ parent_id = self ._parent_map .get (current_id )
48+ if parent_id is None :
49+ break
50+ depth += 1
51+ current_id = parent_id
52+
53+ return depth
54+
55+ def is_ancestor (self , ancestor_id : int , descendant_id : int ) -> bool :
56+ """Check if ancestor_id is an ancestor of descendant_id."""
57+ current_id = descendant_id
58+ visited = set ()
59+
60+ while current_id is not None and current_id not in visited :
61+ if current_id == ancestor_id :
62+ return True
63+ visited .add (current_id )
64+ current_id = self ._parent_map .get (current_id )
65+
66+ return False
67+
68+ def get_chain (self , span_id : int ) -> List [int ]:
69+ """Get the complete ancestor chain for a span (bottom to top)."""
70+ chain = []
71+ current_id = span_id
72+ visited = set ()
73+
74+ while current_id is not None and current_id not in visited :
75+ chain .append (current_id )
76+ visited .add (current_id )
77+ current_id = self ._parent_map .get (current_id )
78+
79+ return chain
80+
81+ def clear (self ) -> None :
82+ """Clear all registered spans."""
83+ self ._spans .clear ()
84+ self ._parent_map .clear ()
85+
86+
87+ # Global span registry instance
88+ _span_registry = SpanRegistry ()
89+
90+
1391class UiPathTracingManager :
1492 """Static utility class to manage tracing implementations and decorated functions."""
1593
@@ -61,43 +139,6 @@ def get_parent_context() -> context.Context:
61139 )
62140 return set_span_in_context (bottom_span )
63141
64- @staticmethod
65- def _build_ancestor_chain (span : ReadableSpan , spans_by_id : dict ) -> List [ReadableSpan ]:
66- """Build a complete ancestor chain by traversing parents.
67-
68- Args:
69- span: The starting span
70- spans_by_id: Dictionary to populate with discovered spans
71-
72- Returns:
73- List of ancestors from bottom (the span) to top (root)
74- """
75- chain = []
76- current = span
77- visited = set ()
78-
79- while current :
80- span_id = current .get_span_context ().span_id
81-
82- # Avoid infinite loops
83- if span_id in visited :
84- break
85- visited .add (span_id )
86-
87- chain .append (current )
88- spans_by_id [span_id ] = current
89-
90- # Move to parent if it exists
91- if current .parent :
92- parent_id = current .parent .span_id
93- # Try to get parent from already known spans
94- current = spans_by_id .get (parent_id )
95- # If not found, we can't traverse further
96- else :
97- break
98-
99- return chain
100-
101142 @staticmethod
102143 def _get_bottom_most_span (
103144 current_span : ReadableSpan , external_span : ReadableSpan
@@ -114,93 +155,68 @@ def _get_bottom_most_span(
114155 logger .info ("=" * 80 )
115156 logger .info ("Determining bottom-most span..." )
116157
117- current_span_id = current_span .get_span_context ().span_id
118- external_span_id = external_span .get_span_context ().span_id
158+ # Register both spans in the registry
159+ _span_registry .register_span (current_span )
160+ _span_registry .register_span (external_span )
119161
120- # Start by adding external ancestors to our known spans
162+ # Also register external ancestors
121163 external_ancestors = UiPathTracingManager .get_ancestor_spans () or []
122- spans_by_id = {}
123164 for ancestor in external_ancestors :
124- spans_by_id [ ancestor . get_span_context (). span_id ] = ancestor
165+ _span_registry . register_span ( ancestor )
125166
126- # Build complete ancestor chains for both spans
127- logger .info ("Building ancestor chain for current_span..." )
128- current_chain = UiPathTracingManager ._build_ancestor_chain (current_span , spans_by_id )
129-
130- logger .info ("Building ancestor chain for external_span..." )
131- external_chain = UiPathTracingManager ._build_ancestor_chain (external_span , spans_by_id )
167+ current_span_id = current_span .get_span_context ().span_id
168+ external_span_id = external_span .get_span_context ().span_id
132169
133- # Build a parent lookup map
134- parent_map = {} # span_id -> parent_id
135- for span_id , span in spans_by_id .items ():
136- parent_map [span_id ] = span .parent .span_id if span .parent else None
170+ # Get chains from registry
171+ current_chain = _span_registry .get_chain (current_span_id )
172+ external_chain = _span_registry .get_chain (external_span_id )
137173
138- logger .info (f"current_span chain length ( depth) : { len ( current_chain )} " )
139- logger .info (f"external_span chain length ( depth) : { len ( external_chain )} " )
174+ logger .info (f"current_span: ' { current_span . name } ' depth: { _span_registry . calculate_depth ( current_span_id )} " )
175+ logger .info (f"external_span: ' { external_span . name } ' depth: { _span_registry . calculate_depth ( external_span_id )} " )
140176
141177 # Log the chains
142178 logger .info ("current_span chain (bottom to top):" )
143- for i , span in enumerate (current_chain ):
144- span_id = span .get_span_context ().span_id
145- parent_id = f"{ span .parent .span_id :016x} " if span .parent else "None"
146- logger .info (f" [{ i } ] { span .name } (id: { span_id :016x} , parent: { parent_id } )" )
179+ for i , span_id in enumerate (current_chain ):
180+ span = _span_registry .get_span (span_id )
181+ if span :
182+ parent_id = _span_registry .get_parent_id (span_id )
183+ parent_str = f"{ parent_id :016x} " if parent_id is not None else "None"
184+ logger .info (f" [{ i } ] { span .name } (id: { span_id :016x} , parent: { parent_str } )" )
147185
148186 logger .info ("external_span chain (bottom to top):" )
149- for i , span in enumerate (external_chain ):
150- span_id = span .get_span_context ().span_id
151- parent_id = f"{ span .parent .span_id :016x} " if span .parent else "None"
152- logger .info (f" [{ i } ] { span .name } (id: { span_id :016x} , parent: { parent_id } )" )
153-
154- # Check if one span is an ancestor of the other by walking parent pointers
155- # Check if external_span is an ancestor of current_span
156- current_is_descendant_of_external = False
157- temp_id = current_span_id
158- depth = 0
159- while temp_id is not None and depth < 100 : # Prevent infinite loops
160- if temp_id == external_span_id :
161- current_is_descendant_of_external = True
162- logger .info (f"current_span is a descendant of external_span (found at depth { depth } )" )
163- break
164- temp_id = parent_map .get (temp_id )
165- depth += 1
166-
167- # Check if current_span is an ancestor of external_span
168- external_is_descendant_of_current = False
169- temp_id = external_span_id
170- depth = 0
171- while temp_id is not None and depth < 100 : # Prevent infinite loops
172- if temp_id == current_span_id :
173- external_is_descendant_of_current = True
174- logger .info (f"external_span is a descendant of current_span (found at depth { depth } )" )
175- break
176- temp_id = parent_map .get (temp_id )
177- depth += 1
178-
179- # If they're in the same hierarchy (one is ancestor of the other)
180- if current_is_descendant_of_external :
181- # current_span is deeper (descendant means further down the tree)
187+ for i , span_id in enumerate (external_chain ):
188+ span = _span_registry .get_span (span_id )
189+ if span :
190+ parent_id = _span_registry .get_parent_id (span_id )
191+ parent_str = f"{ parent_id :016x} " if parent_id is not None else "None"
192+ logger .info (f" [{ i } ] { span .name } (id: { span_id :016x} , parent: { parent_str } )" )
193+
194+ # Check if one span is an ancestor of the other
195+ if _span_registry .is_ancestor (external_span_id , current_span_id ):
182196 logger .info ("RESULT: current_span is a descendant of external_span -> returning current_span (deeper)" )
183197 logger .info ("=" * 80 )
184198 return current_span
185- elif external_is_descendant_of_current :
186- # external_span is deeper (descendant means further down the tree)
199+ elif _span_registry .is_ancestor (current_span_id , external_span_id ):
187200 logger .info ("RESULT: external_span is a descendant of current_span -> returning external_span (deeper)" )
188201 logger .info ("=" * 80 )
189202 return external_span
190203
191204 # Neither is an ancestor of the other - they're in different branches
192- # Use chain length as tiebreaker
193- if len (current_chain ) > len (external_chain ):
194- logger .info (f"RESULT: Different branches, current_span is deeper (chain length { len (current_chain )} > { len (external_chain )} ) -> returning current_span" )
205+ # Use depth as tiebreaker
206+ current_depth = _span_registry .calculate_depth (current_span_id )
207+ external_depth = _span_registry .calculate_depth (external_span_id )
208+
209+ if current_depth > external_depth :
210+ logger .info (f"RESULT: Different branches, current_span is deeper (depth { current_depth } > { external_depth } ) -> returning current_span" )
195211 logger .info ("=" * 80 )
196212 return current_span
197- elif len ( external_chain ) > len ( current_chain ) :
198- logger .info (f"RESULT: Different branches, external_span is deeper (chain length { len ( external_chain ) } > { len ( current_chain ) } ) -> returning external_span" )
213+ elif external_depth > current_depth :
214+ logger .info (f"RESULT: Different branches, external_span is deeper (depth { external_depth } > { current_depth } ) -> returning external_span" )
199215 logger .info ("=" * 80 )
200216 return external_span
201217 else :
202218 # Same depth, different branches - default to external
203- logger .info (f"RESULT: Same depth ({ len ( current_chain ) } ), different branches -> defaulting to external_span" )
219+ logger .info (f"RESULT: Same depth ({ current_depth } ), different branches -> defaulting to external_span" )
204220 logger .info ("=" * 80 )
205221 return external_span
206222
0 commit comments