Home >Backend Development >Python Tutorial >How to Efficiently Log Raw HTTP Request and Response Bodies in FastAPI?

How to Efficiently Log Raw HTTP Request and Response Bodies in FastAPI?

DDD
DDDOriginal
2024-11-30 19:08:17915browse

How to Efficiently Log Raw HTTP Request and Response Bodies in FastAPI?

How to Record Raw HTTP Requests and Responses in Python FastAPI

Introduction:

In order to meet auditing requirements for your Python FastAPI-based web service, you need to preserve the raw JSON bodies of both requests and responses on certain routes. This guide will present two viable solutions to accomplish this without noticeably impacting response times, even when working with body sizes approximating 1MB.

Option 1: Middleware Utilization

Middleware Mechanics:

Middleware functions as a gatekeeper for requests entering the application. It allows for handling requests before endpoint processing and responses before returning to clients. You can establish middleware using the @app.middleware decorator on a function:

Request and Response Body Management:

To access the request body from the stream within middleware (using request.body() or request.stream()), you'll need to make it available later in the request-response cycle. The linked post discusses this workaround, which now is unnecessary for FastAPI versions 0.108.0 and above.

For the response body, you can replicate the technique outlined in this post to consume and return the body directly, providing status code, headers, and media type along with the original response.

Logging Data:

Employ BackgroundTask to log data, ensuring its execution after response completion. This eliminates client waiting for logging tasks and maintains response time integrity.

Option 2: Custom APIRoute Implementation

Custom APIRoute:

This option involves creating a custom APIRoute class for manipulating request and response bodies before processing endpoints or returning results to clients. It enables the isolation of custom route handling to specific endpoints by using a dedicated APIRouter:

Considerations:

Memory Constraints:

Both approaches may encounter challenges with large request or response bodies exceeding available server RAM. Streaming large responses can introduce client-side delays or reverse proxy errors. Restrict middleware usage to specific routes or exclude endpoints with large streaming responses to avoid potential issues.

Example Code (Option 1):

from fastapi import FastAPI, APIRouter, Response, Request
from starlette.background import BackgroundTask
from fastapi.routing import APIRoute
from starlette.types import Message
from typing import Dict, Any
import logging


app = FastAPI()
logging.basicConfig(filename='info.log', level=logging.DEBUG)


def log_info(req_body, res_body):
    logging.info(req_body)
    logging.info(res_body)



# Not required for FastAPI >= 0.108.0
async def set_body(request: Request, body: bytes):
    async def receive() -> Message:
        return {'type': 'http.request', 'body': body}
    request._receive = receive


@app.middleware('http')
async def some_middleware(request: Request, call_next):
    req_body = await request.body()
    await set_body(request, req_body)  # Not required for FastAPI >= 0.108.0
    response = await call_next(request)
    
    res_body = b''
    async for chunk in response.body_iterator:
        res_body += chunk
    
    task = BackgroundTask(log_info, req_body, res_body)
    return Response(content=res_body, status_code=response.status_code, 
        headers=dict(response.headers), media_type=response.media_type, background=task)


@app.post('/')
def main(payload: Dict[Any, Any]):
    return payload

Example Code (Option 2):

from fastapi import FastAPI, APIRouter, Response, Request
from starlette.background import BackgroundTask
from starlette.responses import StreamingResponse
from fastapi.routing import APIRoute
from starlette.types import Message
from typing import Callable, Dict, Any
import logging
import httpx


def log_info(req_body, res_body):
    logging.info(req_body)
    logging.info(res_body)

       
class LoggingRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            req_body = await request.body()
            response = await original_route_handler(request)
            tasks = response.background
            
            if isinstance(response, StreamingResponse):
                res_body = b''
                async for item in response.body_iterator:
                    res_body += item
                  
                task = BackgroundTask(log_info, req_body, res_body)
                response = Response(content=res_body, status_code=response.status_code, 
                        headers=dict(response.headers), media_type=response.media_type)
            else:
                task = BackgroundTask(log_info, req_body, response.body)
            
            # Check if the original response had background tasks already attached to it
            if tasks:
                tasks.add_task(task)  # Add the new task to the tasks list
                response.background = tasks
            else:
                response.background = task
                
            return response
            
        return custom_route_handler


app = FastAPI()
router = APIRouter(route_class=LoggingRoute)
logging.basicConfig(filename='info.log', level=logging.DEBUG)


@router.post('/')
def main(payload: Dict[Any, Any]):
    return payload


@router.get('/video')
def get_video():
    url = 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4'
    
    def gen():
        with httpx.stream('GET', url) as r:
            for chunk in r.iter_raw():
                yield chunk

    return StreamingResponse(gen(), media_type='video/mp4')


app.include_router(router)

These solutions provide efficient methods for logging raw HTTP request and response bodies in FastAPI without significantly affecting response times.

The above is the detailed content of How to Efficiently Log Raw HTTP Request and Response Bodies in FastAPI?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn