@@ -28,33 +28,92 @@ def register_current_span_provider(
2828 cls ._current_span_provider = current_span_provider
2929
3030 @staticmethod
31- def get_parent_context ():
31+ def get_parent_context () -> context . Context :
3232 """Get the parent context for span creation.
3333
3434 Prioritizes:
35- 1. Currently active OTel span (for recursion/children)
36- 2. External span provider (for top-level calls )
37- 3. Current context as fallback
35+ - Bottom-most span when both current_span and external_span exist
36+ - Single available span (current_span or external_span )
37+ - Current context as fallback
3838 """
39- # Always use the currently active OTel span if valid (recursion / children)
4039 current_span = trace .get_current_span ()
40+ has_current_span = current_span is not None and current_span .get_span_context ().is_valid
4141
42- if current_span is not None and current_span .get_span_context ().is_valid :
42+ external_span = UiPathTracingManager .get_external_current_span ()
43+ has_external_span = external_span is not None
44+
45+ # Only one or no spans available
46+ if not has_current_span :
47+ return set_span_in_context (external_span ) if has_external_span else context .get_current ()
48+ if not has_external_span :
4349 return set_span_in_context (current_span )
4450
45- # Only for the very top-level call, fallback to external span provider
51+ # Both spans exist - find the bottom-most one
52+ bottom_span = UiPathTracingManager ._get_bottom_most_span (
53+ current_span , external_span
54+ )
55+ return set_span_in_context (bottom_span )
56+
57+ @staticmethod
58+ def _get_bottom_most_span (current_span : Any , external_span : Any ) -> Any :
59+ """Determine which span is deeper in the ancestor tree.
60+
61+ Args:
62+ current_span: The OTel current span
63+ external_span: The external span from the provider
64+
65+ Returns:
66+ The span that is deeper (closer to the bottom) in the call hierarchy
67+ """
68+ ancestor_spans = UiPathTracingManager .get_ancestor_spans ()
69+ if not ancestor_spans :
70+ return external_span
71+
72+ current_span_id = current_span .get_span_context ().span_id
73+ external_span_id = external_span .get_span_context ().span_id
74+
75+ current_index = None
76+ external_index = None
77+
78+ for i , ancestor in enumerate (ancestor_spans ):
79+ ancestor_id = ancestor .get_span_context ().span_id
80+ if ancestor_id == current_span_id :
81+ current_index = i
82+ if ancestor_id == external_span_id :
83+ external_index = i
84+
85+ # Both in tree: higher index = deeper
86+ if current_index is not None and external_index is not None :
87+ return current_span if current_index > external_index else external_span
88+
89+ # Only one in tree: that one is deeper
90+ if current_index is not None :
91+ return current_span
92+ if external_index is not None :
93+ return external_span
94+
95+ # Neither in tree: default to external
96+ return external_span
97+
98+ @staticmethod
99+ def get_external_current_span ():
100+ """Get the current span from the external provider, if any."""
46101 if UiPathTracingManager ._current_span_provider is not None :
47102 try :
48- external_span = UiPathTracingManager ._current_span_provider ()
49- if external_span is not None :
50- return set_span_in_context (external_span )
103+ return UiPathTracingManager ._current_span_provider ()
51104 except Exception as e :
52105 logger .warning (f"Error getting current span from provider: { e } " )
106+ return None
53107
54- # Last fallback
55- ctx = context .get_current ()
56-
57- return ctx
108+ @classmethod
109+ def get_ancestor_spans (cls ) -> List [Any ]:
110+ """Get the ancestor spans from the registered provider, if any."""
111+ if cls ._current_span_ancestors_provider is not None :
112+ try :
113+ return cls ._current_span_ancestors_provider ()
114+ except Exception as e :
115+ logger .warning (f"Error getting ancestor spans from provider: { e } " )
116+ return []
58117
59118 @classmethod
60119 def register_current_span_ancestors_provider (
0 commit comments