1717 _process_sync_generator ,
1818 _record_entity_input ,
1919 _record_entity_output ,
20+ _extract_request_data ,
21+ _extract_response_data ,
2022)
2123
2224
2325def create_entity_decorator (entity_kind : str ) -> Callable [..., Any ]:
2426 """
2527 Factory that creates decorators for instrumenting functions and classes.
26- Handles different entity kinds (e.g., SESSION, TASK) and function types (sync, async, generator).
28+ Handles different entity kinds (e.g., SESSION, TASK, HTTP ) and function types (sync, async, generator).
2729 """
2830
2931 def decorator (
@@ -34,9 +36,20 @@ def decorator(
3436 tags : Optional [Union [list , dict ]] = None ,
3537 cost = None ,
3638 spec = None ,
39+ capture_request : bool = True ,
40+ capture_response : bool = True ,
3741 ) -> Callable [..., Any ]:
3842 if wrapped is None :
39- return functools .partial (decorator , name = name , version = version , tags = tags , cost = cost , spec = spec )
43+ return functools .partial (
44+ decorator ,
45+ name = name ,
46+ version = version ,
47+ tags = tags ,
48+ cost = cost ,
49+ spec = spec ,
50+ capture_request = capture_request ,
51+ capture_response = capture_response ,
52+ )
4053
4154 if inspect .isclass (wrapped ):
4255 # Class decoration wraps __init__ and aenter/aexit for context management.
@@ -96,7 +109,176 @@ def wrapper(
96109 is_generator = inspect .isgeneratorfunction (wrapped_func )
97110 is_async_generator = inspect .isasyncgenfunction (wrapped_func )
98111
99- if entity_kind == SpanKind .SESSION :
112+ # Special handling for HTTP entity kind
113+ if entity_kind == SpanKind .HTTP :
114+ if is_generator or is_async_generator :
115+ logger .warning (
116+ f"@track_endpoint on generator '{ operation_name } ' is not supported. Use @trace instead."
117+ )
118+ return wrapped_func (* args , ** kwargs )
119+
120+ if is_async :
121+
122+ async def _wrapped_http_async () -> Any :
123+ trace_context : Optional [TraceContext ] = None
124+ try :
125+ # Create main session span
126+ trace_context = tracer .start_trace (trace_name = operation_name , tags = tags )
127+ if not trace_context :
128+ logger .error (
129+ f"Failed to start trace for @track_endpoint '{ operation_name } '. Executing without trace."
130+ )
131+ return await wrapped_func (* args , ** kwargs )
132+
133+ # Create HTTP request span
134+ if capture_request :
135+ with _create_as_current_span (
136+ f"{ operation_name } .request" ,
137+ SpanKind .HTTP ,
138+ version = version ,
139+ attributes = {SpanAttributes .HTTP_METHOD : "REQUEST" }
140+ if SpanAttributes .HTTP_METHOD
141+ else None ,
142+ ) as request_span :
143+ try :
144+ request_data = _extract_request_data ()
145+ if request_data :
146+ # Set HTTP attributes
147+ if hasattr (SpanAttributes , "HTTP_METHOD" ) and request_data .get ("method" ):
148+ request_span .set_attribute (
149+ SpanAttributes .HTTP_METHOD , request_data ["method" ]
150+ )
151+ if hasattr (SpanAttributes , "HTTP_URL" ) and request_data .get ("url" ):
152+ request_span .set_attribute (SpanAttributes .HTTP_URL , request_data ["url" ])
153+
154+ # Record the full request data
155+ _record_entity_input (request_span , (request_data ,), {})
156+ except Exception as e :
157+ logger .warning (f"Failed to record HTTP request for '{ operation_name } ': { e } " )
158+
159+ # Execute the main function
160+ result = await wrapped_func (* args , ** kwargs )
161+
162+ # Create HTTP response span
163+ if capture_response :
164+ with _create_as_current_span (
165+ f"{ operation_name } .response" ,
166+ SpanKind .HTTP ,
167+ version = version ,
168+ attributes = {SpanAttributes .HTTP_METHOD : "RESPONSE" }
169+ if SpanAttributes .HTTP_METHOD
170+ else None ,
171+ ) as response_span :
172+ try :
173+ response_data = _extract_response_data (result )
174+ if response_data :
175+ # Set HTTP attributes
176+ if hasattr (SpanAttributes , "HTTP_STATUS_CODE" ) and response_data .get (
177+ "status_code"
178+ ):
179+ response_span .set_attribute (
180+ SpanAttributes .HTTP_STATUS_CODE , response_data ["status_code" ]
181+ )
182+
183+ # Record the full response data
184+ _record_entity_output (response_span , response_data )
185+ except Exception as e :
186+ logger .warning (f"Failed to record HTTP response for '{ operation_name } ': { e } " )
187+
188+ tracer .end_trace (trace_context , "Success" )
189+ return result
190+ except Exception :
191+ if trace_context :
192+ tracer .end_trace (trace_context , "Indeterminate" )
193+ raise
194+ finally :
195+ if trace_context and trace_context .span .is_recording ():
196+ logger .warning (
197+ f"Trace for @track_endpoint '{ operation_name } ' not explicitly ended. Ending as 'Unknown'."
198+ )
199+ tracer .end_trace (trace_context , "Unknown" )
200+
201+ return _wrapped_http_async ()
202+ else : # Sync function for HTTP
203+ trace_context : Optional [TraceContext ] = None
204+ try :
205+ # Create main session span
206+ trace_context = tracer .start_trace (trace_name = operation_name , tags = tags )
207+ if not trace_context :
208+ logger .error (
209+ f"Failed to start trace for @track_endpoint '{ operation_name } '. Executing without trace."
210+ )
211+ return wrapped_func (* args , ** kwargs )
212+
213+ # Create HTTP request span
214+ if capture_request :
215+ with _create_as_current_span (
216+ f"{ operation_name } .request" ,
217+ SpanKind .HTTP ,
218+ version = version ,
219+ attributes = {SpanAttributes .HTTP_METHOD : "REQUEST" }
220+ if SpanAttributes .HTTP_METHOD
221+ else None ,
222+ ) as request_span :
223+ try :
224+ request_data = _extract_request_data ()
225+ if request_data :
226+ # Set HTTP attributes
227+ if hasattr (SpanAttributes , "HTTP_METHOD" ) and request_data .get ("method" ):
228+ request_span .set_attribute (
229+ SpanAttributes .HTTP_METHOD , request_data ["method" ]
230+ )
231+ if hasattr (SpanAttributes , "HTTP_URL" ) and request_data .get ("url" ):
232+ request_span .set_attribute (SpanAttributes .HTTP_URL , request_data ["url" ])
233+
234+ # Record the full request data
235+ _record_entity_input (request_span , (request_data ,), {})
236+ except Exception as e :
237+ logger .warning (f"Failed to record HTTP request for '{ operation_name } ': { e } " )
238+
239+ # Execute the main function
240+ result = wrapped_func (* args , ** kwargs )
241+
242+ # Create HTTP response span
243+ if capture_response :
244+ with _create_as_current_span (
245+ f"{ operation_name } .response" ,
246+ SpanKind .HTTP ,
247+ version = version ,
248+ attributes = {SpanAttributes .HTTP_METHOD : "RESPONSE" }
249+ if SpanAttributes .HTTP_METHOD
250+ else None ,
251+ ) as response_span :
252+ try :
253+ response_data = _extract_response_data (result )
254+ if response_data :
255+ # Set HTTP attributes
256+ if hasattr (SpanAttributes , "HTTP_STATUS_CODE" ) and response_data .get (
257+ "status_code"
258+ ):
259+ response_span .set_attribute (
260+ SpanAttributes .HTTP_STATUS_CODE , response_data ["status_code" ]
261+ )
262+
263+ # Record the full response data
264+ _record_entity_output (response_span , response_data )
265+ except Exception as e :
266+ logger .warning (f"Failed to record HTTP response for '{ operation_name } ': { e } " )
267+
268+ tracer .end_trace (trace_context , "Success" )
269+ return result
270+ except Exception :
271+ if trace_context :
272+ tracer .end_trace (trace_context , "Indeterminate" )
273+ raise
274+ finally :
275+ if trace_context and trace_context .span .is_recording ():
276+ logger .warning (
277+ f"Trace for @track_endpoint '{ operation_name } ' not explicitly ended. Ending as 'Unknown'."
278+ )
279+ tracer .end_trace (trace_context , "Unknown" )
280+
281+ elif entity_kind == SpanKind .SESSION :
100282 if is_generator or is_async_generator :
101283 logger .warning (
102284 f"@agentops.trace on generator '{ operation_name } ' creates a single span, not a full trace."
0 commit comments