Heim >Backend-Entwicklung >Python-Tutorial >Wie kann man rohe HTTP-Anforderungs- und Antwortkörper in FastAPI effizient protokollieren?

Wie kann man rohe HTTP-Anforderungs- und Antwortkörper in FastAPI effizient protokollieren?

DDD
DDDOriginal
2024-11-30 19:08:17912Durchsuche

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

So zeichnen Sie rohe HTTP-Anfragen und -Antworten in Python FastAPI auf

Einführung:

In Um die Prüfanforderungen für Ihren Python FastAPI-basierten Webdienst zu erfüllen, müssen Sie die rohen JSON-Texte sowohl von Anfragen als auch von Antworten auf bestimmten Routen beibehalten. In diesem Leitfaden werden zwei praktikable Lösungen vorgestellt, um dies zu erreichen, ohne die Antwortzeiten spürbar zu beeinträchtigen, selbst wenn mit Körpergrößen von etwa 1 MB gearbeitet wird.

Option 1: Middleware-Nutzung

Middleware-Mechanik:

Middleware fungiert als Gatekeeper für Anfragen, die in die Anwendung gelangen. Es ermöglicht die Bearbeitung von Anfragen vor der Endpunktverarbeitung und Antworten vor der Rückgabe an Clients. Sie können Middleware mithilfe des @app.middleware-Dekorators für eine Funktion einrichten:

Verwaltung des Anforderungs- und Antworttexts:

Zugriff auf den Anforderungstext aus dem Stream innerhalb der Middleware ( Wenn Sie request.body() oder request.stream() verwenden, müssen Sie es später im Anfrage-Antwort-Zyklus verfügbar machen. Im verlinkten Beitrag wird diese Problemumgehung besprochen, die jetzt für FastAPI-Versionen 0.108.0 und höher nicht mehr erforderlich ist.

Für den Antworttext können Sie die in diesem Beitrag beschriebene Technik replizieren, um den Text direkt zu konsumieren und zurückzugeben und den Status anzugeben Code, Header und Medientyp zusammen mit der ursprünglichen Antwort.

Protokollierung Daten:

Verwenden Sie BackgroundTask, um Daten zu protokollieren und sicherzustellen, dass sie nach Abschluss der Antwort ausgeführt werden. Dadurch entfällt das Warten des Clients auf Protokollierungsaufgaben und die Integrität der Antwortzeit bleibt erhalten.

Option 2: Benutzerdefinierte APIRoute-Implementierung

Benutzerdefinierte APIRoute:

Diese Option beinhaltet das Erstellen einer benutzerdefinierten APIRoute-Klasse zum Bearbeiten von Anforderungs- und Antworttexten vor der Verarbeitung von Endpunkten oder der Rückgabe von Ergebnissen an Clients. Es ermöglicht die Isolierung der benutzerdefinierten Routenbehandlung zu bestimmten Endpunkten durch die Verwendung eines dedizierten APIRouter:

Überlegungen:

Speicherbeschränkungen:

Bei beiden Ansätzen kann es zu Problemen kommen, wenn große Anfrage- oder Antworttexte den verfügbaren Server-RAM überschreiten. Das Streamen großer Antworten kann zu clientseitigen Verzögerungen oder Reverse-Proxy-Fehlern führen. Beschränken Sie die Middleware-Nutzung auf bestimmte Routen oder schließen Sie Endpunkte mit großen Streaming-Antworten aus, um potenzielle Probleme zu vermeiden.

Beispielcode (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

Beispiel 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)

Diese Lösungen bieten effiziente Methoden zum Protokollieren von Roh-HTTP-Anfragen und Antwortkörper in FastAPI, ohne die Antwortzeiten wesentlich zu beeinträchtigen.

Das obige ist der detaillierte Inhalt vonWie kann man rohe HTTP-Anforderungs- und Antwortkörper in FastAPI effizient protokollieren?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn