Data science/Python

FastAPI request body 로깅

연금(Pension)술사 2023. 10. 26. 10:46

요약


FastAPI middleware은 어플리케이션의 요청 및 응답처리과정중에 중간에 위치하여, 여러 작업을 수행할 수 있는 요청->동작 사이, 응답->동작사이에 여러 기능을 수행
주로 요청 및 응답을 변형하거나(예, 암호화), 인증, 로깅및 모니터링, 예외처리등을 수행.
ASGI (Asynchronous Server Gateway Interface) 프로토콜을 따르는 웹 애플리케이션에서 비동기 요청을 처리하기 위해, request._receive속성에 설정하도록함


fastAPI의 미들웨어는 @app.middleware("http")와같이 함수위의 데코레이터로 사용가능합니다.
async def set_body(request: Request, body: bytes) 함수는 원래 요청 객체에 바이트로 된 본문을 설정하기 위해 사용됩니다. 
아래의 함수 내에서 async def receive() -> Message 함수를 정의하고, 이 함수는 request 객체의 _receive 속성으로 설정됩니다. 
따라서 ASGI 프레임워크는 요청을 수신할 때 이 _receive 함수를 호출하여 요청 객체의 본문을 설정하여 로깅할 수 있습니다.

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 log_request_payload(request: Request, call_next):
    payload = await request.body()
    await set_body(request, payload)
    request.app.state.logger.debug(f"Request payload: {payload.decode()}")
    response = await call_next(request)
    return response

 



아래의 call_next의 인자가 callable로 (reqeust)을다시 인자로 받아야하는데, FastAPI.reqeust은 아래와 같이 구성되어있습니다.
request._receive 속성이 property로 설정되어있고 타입이 empty_receive라 Courutine의 타입을 받게되어있습니다.
그래서 async def로 정의한 함수를 넣어주도록하고, 그 메시지의 형태를 stream내애 얻어지는 형태랑 유사하게 받도록합니다.

class Request(HTTPConnection):
    _form: typing.Optional[FormData]

    def __init__(
        self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send
    ):
        super().__init__(scope)
        assert scope["type"] == "http"
        self._receive = receive
        self._send = send
        self._stream_consumed = False
        self._is_disconnected = False
        self._form = None

    @property
    def method(self) -> str:
        return self.scope["method"]

    @property
    def receive(self) -> Receive:
        return self._receive

    async def stream(self) -> typing.AsyncGenerator[bytes, None]:
        if hasattr(self, "_body"):
            yield self._body
            yield b""
            return
        if self._stream_consumed:
            raise RuntimeError("Stream consumed")
        self._stream_consumed = True
        while True:
            message = await self._receive()
            if message["type"] == "http.request":
                body = message.get("body", b"")
                if body:
                    yield body
                if not message.get("more_body", False):
                    break
            elif message["type"] == "http.disconnect":
                self._is_disconnected = True
                raise ClientDisconnect()
        yield b""
반응형