22Request context middleware for automatic trace_id injection.
33"""
44
5+ import json
56import time
67
78from collections .abc import Callable
@@ -26,6 +27,43 @@ def extract_trace_id_from_headers(request: Request) -> str | None:
2627 return None
2728
2829
30+ async def get_request_params (request : Request ) -> tuple [dict , bytes | None ]:
31+ """
32+ Extract request parameters (query params and body) for logging.
33+
34+ Args:
35+ request: The incoming request object
36+
37+ Returns:
38+ Tuple of (params_dict, body_bytes). body_bytes is None if body was not read.
39+ """
40+ params_log = {}
41+
42+ # Get query parameters
43+ if request .query_params :
44+ params_log ["query_params" ] = dict (request .query_params )
45+
46+ # Get request body for requests with body
47+ body_bytes = None
48+ content_type = request .headers .get ("content-type" , "" )
49+ if request .method in ("POST" , "PUT" , "PATCH" , "DELETE" ) and content_type :
50+ try :
51+ body_bytes = await request .body ()
52+ if body_bytes :
53+ if "application/json" in content_type .lower ():
54+ try :
55+ params_log ["body" ] = json .loads (body_bytes )
56+ except (json .JSONDecodeError , UnicodeDecodeError ) as e :
57+ params_log ["body" ] = f"<unable to parse JSON: { e !s} >"
58+ else :
59+ # For non-JSON requests, log body size only
60+ params_log ["body_size" ] = len (body_bytes )
61+ except Exception as e :
62+ logger .error (f"Failed to read request body: { e } " )
63+
64+ return params_log , body_bytes
65+
66+
2967class RequestContextMiddleware (BaseHTTPMiddleware ):
3068 """
3169 Middleware to automatically inject request context for every HTTP request.
@@ -55,14 +93,22 @@ async def dispatch(self, request: Request, call_next: Callable) -> Response:
5593 )
5694 set_request_context (context )
5795
58- # Log request start with parameters
59- params_log = {}
96+ # Get request parameters for logging
97+ params_log , body_bytes = await get_request_params ( request )
6098
61- # Get query parameters
62- if request . query_params :
63- params_log [ "query_params" ] = dict ( request . query_params )
99+ # Re-create the request receive function if body was read
100+ # This ensures downstream handlers can still read the body
101+ if body_bytes is not None :
64102
65- logger .info (f"Request started, params: { params_log } , headers: { request .headers } " )
103+ async def receive ():
104+ return {"type" : "http.request" , "body" : body_bytes , "more_body" : False }
105+
106+ request ._receive = receive
107+
108+ logger .info (
109+ f"Request started, method: { request .method } , path: { request .url .path } , "
110+ f"request params: { params_log } , headers: { request .headers } "
111+ )
66112
67113 # Process the request
68114 try :
0 commit comments