@@ -90,8 +90,10 @@ pub struct GatewayState {
9090 pub mirrors : HashMap < String , Arc < crate :: service:: TrafficMirror > > ,
9191 /// Failover selectors: service_name → FailoverSelector
9292 pub failovers : HashMap < String , Arc < crate :: service:: FailoverSelector > > ,
93- /// Structured access log
93+ /// Structured access log (counter + background task target)
9494 pub access_log : Arc < crate :: observability:: access_log:: AccessLog > ,
95+ /// Channel for fire-and-forget log entries — background task does JSON + tracing
96+ pub log_tx : tokio:: sync:: mpsc:: UnboundedSender < crate :: observability:: access_log:: AccessLogEntry > ,
9597 /// Sticky session managers (only for services with sticky config)
9698 pub sticky_managers : HashMap < String , Arc < StickySessionManager > > ,
9799 /// Passive health checkers for all services
@@ -327,7 +329,7 @@ async fn start_tcp_entrypoint(
327329 tokio:: spawn ( async move {
328330 let _permit = permit;
329331
330- let headers = HashMap :: new ( ) ;
332+ let headers = http :: HeaderMap :: new ( ) ;
331333 if let Some ( route) = state
332334 . router_table
333335 . match_request ( None , "/" , "TCP" , & headers, & ep_name)
@@ -381,7 +383,7 @@ async fn start_udp_entrypoint(
381383 max_sessions : Option < usize > ,
382384 state : Arc < GatewayState > ,
383385) -> Result < tokio:: task:: JoinHandle < ( ) > > {
384- let headers = HashMap :: new ( ) ;
386+ let headers = http :: HeaderMap :: new ( ) ;
385387 let upstream_addr = state
386388 . router_table
387389 . match_request ( None , "/" , "UDP" , & headers, & name)
@@ -446,14 +448,6 @@ async fn handle_http_request(
446448 let method_str = req. method ( ) . as_str ( ) . to_string ( ) ;
447449 let uri = req. uri ( ) . clone ( ) ;
448450
449- // Collect headers into a plain map for routing and middleware context.
450- let mut header_map = HashMap :: new ( ) ;
451- for ( key, value) in req. headers ( ) . iter ( ) {
452- if let Ok ( v) = value. to_str ( ) {
453- header_map. insert ( key. as_str ( ) . to_string ( ) , v. to_string ( ) ) ;
454- }
455- }
456-
457451 // Detect protocol from request headers.
458452 let is_ws = crate :: proxy:: websocket:: is_websocket_upgrade ( req. headers ( ) ) ;
459453 let is_grpc = crate :: proxy:: grpc:: is_grpc_request ( req. headers ( ) ) ;
@@ -462,7 +456,7 @@ async fn handle_http_request(
462456 let access_tracker = state. access_log . start_request ( ) ;
463457
464458 // Extract incoming trace context and create a child span.
465- let trace_ctx = crate :: observability:: tracing:: extract_trace_context ( & header_map )
459+ let trace_ctx = crate :: observability:: tracing:: extract_trace_context ( req . headers ( ) )
466460 . map ( |ctx| ctx. child ( ) )
467461 . unwrap_or_else ( crate :: observability:: tracing:: TraceContext :: new_root) ;
468462
@@ -471,7 +465,7 @@ async fn handle_http_request(
471465 host. as_deref ( ) ,
472466 & path,
473467 & method_str,
474- & header_map ,
468+ req . headers ( ) ,
475469 & entrypoint,
476470 ) {
477471 Some ( route) => route,
@@ -644,8 +638,10 @@ async fn handle_http_request(
644638 . sticky_managers
645639 . get ( & route. service_name )
646640 . and_then ( |mgr| {
647- let session_id = header_map
641+ let session_id = req_parts
642+ . headers
648643 . get ( "cookie" )
644+ . and_then ( |v| v. to_str ( ) . ok ( ) )
649645 . and_then ( |cookie| mgr. extract_session_id ( cookie) )
650646 . map ( |s| s. to_string ( ) ) ;
651647 match mgr. select_backend ( session_id. as_deref ( ) , lb. backends ( ) ) {
@@ -751,7 +747,7 @@ async fn handle_http_request(
751747 {
752748 Ok ( grpc_resp) => {
753749 let status_code = grpc_resp. http_status . as_u16 ( ) ;
754- state. access_log . record ( & access_tracker. build_entry (
750+ let _ = state. log_tx . send ( access_tracker. build_entry (
755751 remote_addr. ip ( ) . to_string ( ) ,
756752 method_str,
757753 path,
@@ -761,7 +757,7 @@ async fn handle_http_request(
761757 Some ( backend. url . clone ( ) ) ,
762758 Some ( route. router_name . clone ( ) ) ,
763759 Some ( entrypoint) ,
764- header_map . get ( "user-agent" ) . cloned ( ) ,
760+ req_parts . headers . get ( "user-agent" ) . and_then ( |v| v . to_str ( ) . ok ( ) ) . map ( |s| s . to_string ( ) ) ,
765761 ) ) ;
766762
767763 // Record passive health from HTTP status.
@@ -806,7 +802,7 @@ async fn handle_http_request(
806802 }
807803 Err ( e) => {
808804 tracing:: error!( error = %e, backend = backend. url, "gRPC proxy error" ) ;
809- state. access_log . record ( & access_tracker. build_entry (
805+ let _ = state. log_tx . send ( access_tracker. build_entry (
810806 remote_addr. ip ( ) . to_string ( ) ,
811807 method_str,
812808 path,
@@ -816,7 +812,7 @@ async fn handle_http_request(
816812 Some ( backend. url . clone ( ) ) ,
817813 Some ( route. router_name . clone ( ) ) ,
818814 Some ( entrypoint) ,
819- header_map . get ( "user-agent" ) . cloned ( ) ,
815+ req_parts . headers . get ( "user-agent" ) . and_then ( |v| v . to_str ( ) . ok ( ) ) . map ( |s| s . to_string ( ) ) ,
820816 ) ) ;
821817 if let Some ( phc) = state. passive_health . get ( & route. service_name ) {
822818 phc. record_error ( & backend, 502 ) ;
@@ -864,7 +860,7 @@ async fn handle_http_request(
864860 {
865861 Ok ( stream_resp) => {
866862 let status_code = stream_resp. status . as_u16 ( ) ;
867- state. access_log . record ( & access_tracker. build_entry (
863+ let _ = state. log_tx . send ( access_tracker. build_entry (
868864 remote_addr. ip ( ) . to_string ( ) ,
869865 method_str,
870866 path,
@@ -874,7 +870,7 @@ async fn handle_http_request(
874870 Some ( backend. url . clone ( ) ) ,
875871 Some ( route. router_name . clone ( ) ) ,
876872 Some ( entrypoint) ,
877- header_map . get ( "user-agent" ) . cloned ( ) ,
873+ req_parts . headers . get ( "user-agent" ) . and_then ( |v| v . to_str ( ) . ok ( ) ) . map ( |s| s . to_string ( ) ) ,
878874 ) ) ;
879875
880876 if let Some ( phc) = state. passive_health . get ( & route. service_name ) {
@@ -982,7 +978,7 @@ async fn handle_http_request(
982978 Ok ( proxy_resp) => {
983979 let status_code = proxy_resp. status . as_u16 ( ) ;
984980
985- state. access_log . record ( & access_tracker. build_entry (
981+ let _ = state. log_tx . send ( access_tracker. build_entry (
986982 remote_addr. ip ( ) . to_string ( ) ,
987983 method_str,
988984 path,
@@ -992,7 +988,7 @@ async fn handle_http_request(
992988 Some ( backend. url . clone ( ) ) ,
993989 Some ( route. router_name . clone ( ) ) ,
994990 Some ( entrypoint) ,
995- header_map . get ( "user-agent" ) . cloned ( ) ,
991+ req_parts . headers . get ( "user-agent" ) . and_then ( |v| v . to_str ( ) . ok ( ) ) . map ( |s| s . to_string ( ) ) ,
996992 ) ) ;
997993
998994 // Passive health: record 5xx errors.
@@ -1051,7 +1047,7 @@ async fn handle_http_request(
10511047 phc. record_error ( & backend, 502 ) ;
10521048 }
10531049
1054- state. access_log . record ( & access_tracker. build_entry (
1050+ let _ = state. log_tx . send ( access_tracker. build_entry (
10551051 remote_addr. ip ( ) . to_string ( ) ,
10561052 method_str,
10571053 path,
@@ -1061,7 +1057,7 @@ async fn handle_http_request(
10611057 Some ( backend. url . clone ( ) ) ,
10621058 Some ( route. router_name . clone ( ) ) ,
10631059 Some ( entrypoint) ,
1064- header_map . get ( "user-agent" ) . cloned ( ) ,
1060+ req_parts . headers . get ( "user-agent" ) . and_then ( |v| v . to_str ( ) . ok ( ) ) . map ( |s| s . to_string ( ) ) ,
10651061 ) ) ;
10661062
10671063 state. metrics . record_request ( 502 , 0 ) ;
@@ -1130,6 +1126,7 @@ mod tests {
11301126 mirrors : HashMap :: new ( ) ,
11311127 failovers : HashMap :: new ( ) ,
11321128 access_log : Arc :: new ( crate :: observability:: access_log:: AccessLog :: new ( ) ) ,
1129+ log_tx : tokio:: sync:: mpsc:: unbounded_channel ( ) . 0 ,
11331130 sticky_managers : HashMap :: new ( ) ,
11341131 passive_health : HashMap :: new ( ) ,
11351132 metrics : Arc :: new ( crate :: observability:: metrics:: GatewayMetrics :: new ( ) ) ,
0 commit comments