11"""Tracing decorators for function instrumentation."""
22
33import inspect
4- import json
54import logging
65import random
76from functools import wraps
1211from opentelemetry .trace .status import StatusCode
1312
1413from uipath .core .tracing ._utils import (
15- format_args_for_trace_json ,
16- format_object_for_trace_json ,
1714 get_supported_params ,
15+ set_span_input_attributes ,
16+ set_span_output_attributes ,
1817)
1918from uipath .core .tracing .span_utils import UiPathSpanUtils
2019
@@ -45,7 +44,7 @@ def _opentelemetry_traced(
4544 recording: If False, span is not recorded
4645 """
4746
48- def decorator (func ) :
47+ def decorator (func : Callable [..., Any ]) -> Callable [..., Any ] :
4948 trace_name = name or func .__name__
5049
5150 def get_span ():
@@ -78,38 +77,30 @@ def get_span():
7877
7978 # --------- Sync wrapper ---------
8079 @wraps (func )
81- def sync_wrapper (* args , ** kwargs ) :
80+ def sync_wrapper (* args : Any , ** kwargs : Any ) -> Any :
8281 span_cm , span = get_span ()
8382 try :
84- # Check if this should be treated as a tool call
85- is_tool = span_type and span_type .upper () == "TOOL"
86-
87- if is_tool :
88- # Set OpenInference tool call attributes
89- span .set_attribute ("openinference.span.kind" , "TOOL" )
90- span .set_attribute ("tool.name" , trace_name )
91- span .set_attribute ("span_type" , "TOOL" )
92- else :
93- span .set_attribute ("span_type" , span_type or "function_call_sync" )
94-
95- if run_type is not None :
96- span .set_attribute ("run_type" , run_type )
97-
98- inputs = format_args_for_trace_json (
99- inspect .signature (func ), * args , ** kwargs
83+ # Set input attributes BEFORE execution
84+ set_span_input_attributes (
85+ span ,
86+ trace_name = trace_name ,
87+ wrapped_func = func ,
88+ args = args ,
89+ kwargs = kwargs ,
90+ run_type = run_type ,
91+ span_type = span_type or "function_call_sync" ,
92+ input_processor = input_processor ,
10093 )
101- if input_processor :
102- processed_inputs = input_processor (json .loads (inputs ))
103- inputs = json .dumps (processed_inputs , default = str )
104-
105- span .set_attribute ("input.mime_type" , "application/json" )
106- span .set_attribute ("input.value" , inputs )
10794
95+ # Execute the function
10896 result = func (* args , ** kwargs )
109- output = output_processor (result ) if output_processor else result
11097
111- span .set_attribute ("output.value" , format_object_for_trace_json (output ))
112- span .set_attribute ("output.mime_type" , "application/json" )
98+ # Set output attributes AFTER execution
99+ set_span_output_attributes (
100+ span ,
101+ result = result ,
102+ output_processor = output_processor ,
103+ )
113104 return result
114105 except Exception as e :
115106 span .record_exception (e )
@@ -121,38 +112,30 @@ def sync_wrapper(*args, **kwargs):
121112
122113 # --------- Async wrapper ---------
123114 @wraps (func )
124- async def async_wrapper (* args , ** kwargs ) :
115+ async def async_wrapper (* args : Any , ** kwargs : Any ) -> Any :
125116 span_cm , span = get_span ()
126117 try :
127- # Check if this should be treated as a tool call
128- is_tool = span_type and span_type .upper () == "TOOL"
129-
130- if is_tool :
131- # Set OpenInference tool call attributes
132- span .set_attribute ("openinference.span.kind" , "TOOL" )
133- span .set_attribute ("tool.name" , trace_name )
134- span .set_attribute ("span_type" , "TOOL" )
135- else :
136- span .set_attribute ("span_type" , span_type or "function_call_async" )
137-
138- if run_type is not None :
139- span .set_attribute ("run_type" , run_type )
140-
141- inputs = format_args_for_trace_json (
142- inspect .signature (func ), * args , ** kwargs
118+ # Set input attributes BEFORE execution
119+ set_span_input_attributes (
120+ span ,
121+ trace_name = trace_name ,
122+ wrapped_func = func ,
123+ args = args ,
124+ kwargs = kwargs ,
125+ run_type = run_type ,
126+ span_type = span_type or "function_call_async" ,
127+ input_processor = input_processor ,
143128 )
144- if input_processor :
145- processed_inputs = input_processor (json .loads (inputs ))
146- inputs = json .dumps (processed_inputs , default = str )
147-
148- span .set_attribute ("input.mime_type" , "application/json" )
149- span .set_attribute ("input.value" , inputs )
150129
130+ # Execute the function
151131 result = await func (* args , ** kwargs )
152- output = output_processor (result ) if output_processor else result
153132
154- span .set_attribute ("output.value" , format_object_for_trace_json (output ))
155- span .set_attribute ("output.mime_type" , "application/json" )
133+ # Set output attributes AFTER execution
134+ set_span_output_attributes (
135+ span ,
136+ result = result ,
137+ output_processor = output_processor ,
138+ )
156139 return result
157140 except Exception as e :
158141 span .record_exception (e )
@@ -164,42 +147,34 @@ async def async_wrapper(*args, **kwargs):
164147
165148 # --------- Generator wrapper ---------
166149 @wraps (func )
167- def generator_wrapper (* args , ** kwargs ) :
150+ def generator_wrapper (* args : Any , ** kwargs : Any ) -> Any :
168151 span_cm , span = get_span ()
169152 try :
170- # Check if this should be treated as a tool call
171- is_tool = span_type and span_type .upper () == "TOOL"
172-
173- if is_tool :
174- # Set OpenInference tool call attributes
175- span .set_attribute ("openinference.span.kind" , "TOOL" )
176- span .set_attribute ("tool.name" , trace_name )
177- span .set_attribute ("span_type" , "TOOL" )
178- else :
179- span .set_attribute (
180- "span_type" , span_type or "function_call_generator_sync"
181- )
182-
183- if run_type is not None :
184- span .set_attribute ("run_type" , run_type )
185-
186- inputs = format_args_for_trace_json (
187- inspect .signature (func ), * args , ** kwargs
153+ # Set input attributes BEFORE execution
154+ set_span_input_attributes (
155+ span ,
156+ trace_name = trace_name ,
157+ wrapped_func = func ,
158+ args = args ,
159+ kwargs = kwargs ,
160+ run_type = run_type ,
161+ span_type = span_type or "function_call_generator_sync" ,
162+ input_processor = input_processor ,
188163 )
189- if input_processor :
190- processed_inputs = input_processor (json .loads (inputs ))
191- inputs = json .dumps (processed_inputs , default = str )
192- span .set_attribute ("input.mime_type" , "application/json" )
193- span .set_attribute ("input.value" , inputs )
194164
165+ # Execute the generator and collect outputs
195166 outputs = []
196167 for item in func (* args , ** kwargs ):
197168 outputs .append (item )
198169 span .add_event (f"Yielded: { item } " )
199170 yield item
200- output = output_processor (outputs ) if output_processor else outputs
201- span .set_attribute ("output.value" , format_object_for_trace_json (output ))
202- span .set_attribute ("output.mime_type" , "application/json" )
171+
172+ # Set output attributes AFTER execution
173+ set_span_output_attributes (
174+ span ,
175+ result = outputs ,
176+ output_processor = output_processor ,
177+ )
203178 except Exception as e :
204179 span .record_exception (e )
205180 span .set_status (StatusCode .ERROR , str (e ))
@@ -210,44 +185,34 @@ def generator_wrapper(*args, **kwargs):
210185
211186 # --------- Async generator wrapper ---------
212187 @wraps (func )
213- async def async_generator_wrapper (* args , ** kwargs ) :
188+ async def async_generator_wrapper (* args : Any , ** kwargs : Any ) -> Any :
214189 span_cm , span = get_span ()
215190 try :
216- # Check if this should be treated as a tool call
217- is_tool = span_type and span_type .upper () == "TOOL"
218-
219- if is_tool :
220- # Set OpenInference tool call attributes
221- span .set_attribute ("openinference.span.kind" , "TOOL" )
222- span .set_attribute ("tool.name" , trace_name )
223- span .set_attribute ("span_type" , "TOOL" )
224- else :
225- span .set_attribute (
226- "span_type" , span_type or "function_call_generator_async"
227- )
228-
229- if run_type is not None :
230- span .set_attribute ("run_type" , run_type )
231-
232- inputs = format_args_for_trace_json (
233- inspect .signature (func ), * args , ** kwargs
191+ # Set input attributes BEFORE execution
192+ set_span_input_attributes (
193+ span ,
194+ trace_name = trace_name ,
195+ wrapped_func = func ,
196+ args = args ,
197+ kwargs = kwargs ,
198+ run_type = run_type ,
199+ span_type = span_type or "function_call_generator_async" ,
200+ input_processor = input_processor ,
234201 )
235- if input_processor :
236- processed_inputs = input_processor (json .loads (inputs ))
237- inputs = json .dumps (processed_inputs , default = str )
238-
239- span .set_attribute ("input.mime_type" , "application/json" )
240- span .set_attribute ("input.value" , inputs )
241202
203+ # Execute the generator and collect outputs
242204 outputs = []
243205 async for item in func (* args , ** kwargs ):
244206 outputs .append (item )
245207 span .add_event (f"Yielded: { item } " )
246208 yield item
247209
248- output = output_processor (outputs ) if output_processor else outputs
249- span .set_attribute ("output.value" , format_object_for_trace_json (output ))
250- span .set_attribute ("output.mime_type" , "application/json" )
210+ # Set output attributes AFTER execution
211+ set_span_output_attributes (
212+ span ,
213+ result = outputs ,
214+ output_processor = output_processor ,
215+ )
251216 except Exception as e :
252217 span .record_exception (e )
253218 span .set_status (StatusCode .ERROR , str (e ))
@@ -294,11 +259,11 @@ def traced(
294259 """
295260
296261 # Apply default processors selectively based on hide flags
297- def _default_input_processor (inputs ) :
262+ def _default_input_processor (inputs : Any ) -> Any :
298263 """Default input processor that doesn't log any actual input data."""
299264 return {"redacted" : "Input data not logged for privacy/security" }
300265
301- def _default_output_processor (outputs ) :
266+ def _default_output_processor (outputs : Any ) -> Any :
302267 """Default output processor that doesn't log any actual output data."""
303268 return {"redacted" : "Output data not logged for privacy/security" }
304269
@@ -319,7 +284,7 @@ def _default_output_processor(outputs):
319284
320285 tracer_impl = _opentelemetry_traced
321286
322- def decorator (func ) :
287+ def decorator (func : Callable [..., Any ]) -> Callable [..., Any ] :
323288 # Check which parameters are supported by the tracer_impl
324289 supported_params = get_supported_params (tracer_impl , params )
325290
0 commit comments