@@ -181,6 +181,9 @@ defmodule HTTP do
181181 # Extract AbortController PID from FetchOptions
182182 abort_controller_pid = options . signal
183183
184+ # Emit telemetry event for request start
185+ HTTP.Telemetry . request_start ( request . method , request . url , request . headers )
186+
184187 # Spawn a task to handle the asynchronous HTTP request
185188 task =
186189 Task.Supervisor . async_nolink (
@@ -221,6 +224,8 @@ defmodule HTTP do
221224 pid ( ) | nil
222225 ) :: Response . t ( ) | { :error , term ( ) }
223226 def handle_async_request ( request , _calling_pid , abort_controller_pid ) do
227+ start_time = System . monotonic_time ( :microsecond )
228+
224229 # Use a try/catch block to convert `throw` from handle_httpc_response into an {:error, reason} tuple
225230 try do
226231 case Request . to_httpc_args ( request ) do
@@ -237,18 +242,46 @@ defmodule HTTP do
237242 end
238243
239244 # Handle response (simplified - streaming handled in handle_httpc_response)
240- handle_response ( request_id , request . url )
245+ result = handle_response ( request_id , request . url )
246+
247+ # Emit telemetry event for request completion
248+ duration = System . monotonic_time ( :microsecond ) - start_time
249+
250+ case result do
251+ % Response { status: status , body: body } when is_binary ( body ) ->
252+ HTTP.Telemetry . request_stop ( status , request . url , byte_size ( body ) , duration )
253+
254+ % Response { status: status , stream: nil } ->
255+ # Non-streaming response with nil body (unlikely, but handle)
256+ HTTP.Telemetry . request_stop ( status , request . url , 0 , duration )
257+
258+ % Response { status: status } ->
259+ # Streaming response - we'll emit telemetry when streaming completes
260+ HTTP.Telemetry . request_stop ( status , request . url , 0 , duration )
261+
262+ { :error , _ } ->
263+ # Error will be handled in catch block
264+ :ok
265+ end
266+
267+ result
241268
242269 { :error , reason } ->
270+ duration = System . monotonic_time ( :microsecond ) - start_time
271+ HTTP.Telemetry . request_exception ( request . url , reason , duration )
243272 throw ( reason )
244273 end
245274
246275 # Fallback for unexpected return from Request.to_httpc_args
247276 other_args ->
277+ duration = System . monotonic_time ( :microsecond ) - start_time
278+ HTTP.Telemetry . request_exception ( request . url , { :bad_request_args , other_args } , duration )
248279 throw ( { :bad_request_args , other_args } )
249280 end
250281 catch
251282 reason ->
283+ duration = System . monotonic_time ( :microsecond ) - start_time
284+ HTTP.Telemetry . request_exception ( request . url , reason , duration )
252285 { :error , reason }
253286 end
254287 end
@@ -308,22 +341,33 @@ defmodule HTTP do
308341 defp should_use_streaming? ( content_length ) do
309342 # Stream responses larger than 5MB to avoid issues with large files
310343 case Integer . parse ( content_length || "" ) do
311- { size , _ } when size > 5_000_000 -> true
344+ { size , _ } when size > 5_000_000 ->
345+ # Emit telemetry for streaming start
346+ HTTP.Telemetry . streaming_start ( size )
347+ true
348+
312349 # Stream when size is unknown
313- _ -> content_length == nil
350+ _ ->
351+ if content_length == nil do
352+ HTTP.Telemetry . streaming_start ( 0 )
353+ end
354+
355+ content_length == nil
314356 end
315357 end
316358
317359 defp start_httpc_stream_process ( uri , headers ) do
360+ start_time = System . monotonic_time ( :microsecond )
361+
318362 { :ok , pid } =
319363 Task . start_link ( fn ->
320- stream_httpc_response ( uri , headers )
364+ stream_httpc_response ( uri , headers , start_time )
321365 end )
322366
323367 { :ok , pid }
324368 end
325369
326- defp stream_httpc_response ( uri , headers ) do
370+ defp stream_httpc_response ( uri , headers , start_time ) do
327371 # Use the URI directly (it's already parsed)
328372 _host = uri . host
329373 _port = uri . port || 80
@@ -342,45 +386,62 @@ defmodule HTTP do
342386 sync: false
343387 ) do
344388 { :ok , request_id } ->
345- stream_loop ( request_id , self ( ) )
389+ stream_loop ( request_id , self ( ) , 0 , start_time )
346390
347391 { :error , reason } ->
348392 send ( self ( ) , { :stream_error , self ( ) , reason } )
349393 end
350394 end
351395
352- defp stream_loop ( request_id , caller ) do
396+ defp stream_loop ( request_id , caller , total_bytes , start_time ) do
353397 receive do
354398 { :http , { ^ request_id , { :http_response , _http_version , _status , _reason } } } ->
355- stream_loop ( request_id , caller )
399+ stream_loop ( request_id , caller , total_bytes , start_time )
356400
357401 { :http , { ^ request_id , { :http_header , _ , _header_name , _ , _header_value } } } ->
358- stream_loop ( request_id , caller )
402+ stream_loop ( request_id , caller , total_bytes , start_time )
359403
360404 { :http , { ^ request_id , :http_eoh } } ->
361- stream_loop ( request_id , caller )
405+ stream_loop ( request_id , caller , total_bytes , start_time )
362406
363407 { :http , { ^ request_id , { :http_error , reason } } } ->
364408 send ( caller , { :stream_error , self ( ) , reason } )
365409
366410 { :http , { ^ request_id , :stream_end } } ->
411+ duration = System . monotonic_time ( :microsecond ) - start_time
412+ HTTP.Telemetry . streaming_stop ( total_bytes , duration )
367413 send ( caller , { :stream_end , self ( ) } )
368414
369415 { :http , { ^ request_id , { :http_chunk , chunk } } } ->
416+ chunk_size = byte_size ( chunk )
417+ new_total = total_bytes + chunk_size
418+ HTTP.Telemetry . streaming_chunk ( chunk_size , new_total )
370419 send ( caller , { :stream_chunk , self ( ) , to_string ( chunk ) } )
371- stream_loop ( request_id , caller )
420+ stream_loop ( request_id , caller , new_total , start_time )
372421
373422 { :http , { ^ request_id , { :http_body , body } } } ->
423+ chunk_size = byte_size ( body )
424+ new_total = total_bytes + chunk_size
425+ HTTP.Telemetry . streaming_chunk ( chunk_size , new_total )
374426 send ( caller , { :stream_chunk , self ( ) , to_string ( body ) } )
427+ duration = System . monotonic_time ( :microsecond ) - start_time
428+ HTTP.Telemetry . streaming_stop ( new_total , duration )
375429 send ( caller , { :stream_end , self ( ) } )
376430
377431 { :http , { ^ request_id , { _status_line , _headers , body } } } ->
378432 # Handle complete response (non-streaming case)
379433 binary_body = if is_list ( body ) , do: IO . iodata_to_binary ( body ) , else: body
434+ chunk_size = byte_size ( binary_body )
435+ new_total = total_bytes + chunk_size
436+ HTTP.Telemetry . streaming_chunk ( chunk_size , new_total )
380437 send ( caller , { :stream_chunk , self ( ) , binary_body } )
438+ duration = System . monotonic_time ( :microsecond ) - start_time
439+ HTTP.Telemetry . streaming_stop ( new_total , duration )
381440 send ( caller , { :stream_end , self ( ) } )
382441 after
383442 60_000 ->
443+ duration = System . monotonic_time ( :microsecond ) - start_time
444+ HTTP.Telemetry . streaming_stop ( total_bytes , duration )
384445 send ( caller , { :stream_error , self ( ) , :timeout } )
385446 end
386447 end
0 commit comments