본문 바로가기

개발하자

글로벌 '예외'를 빠른 속도로 포착API

반응형

글로벌 '예외'를 빠른 속도로 포착API

나는 글로벌 차원에서 처리되지 않은 예외를 잡으려고 노력하고 있다. 파일 어딘가에 다음과 같은 정보가 있습니다:

@app.exception_handler(Exception)
async def exception_callback(request: Request, exc: Exception):
  logger.error(exc.detail)

그러나 위의 방법은 실행되지 않습니다. 그러나 아래와 같이 사용자 지정 예외를 작성하고 이를 파악하려고 하면 문제가 없습니다.

class MyException(Exception):
  #some code

@app.exception_handler(MyException)
async def exception_callback(request: Request, exc: MyException):
  logger.error(exc.detail)

제가 해봤는데 이 버그는 요청 본문에 접근하는 것에 대해 말해요. 이 버그를 보고 나는 잡을 수 있어야 한다고 생각한다. 내가 사용하고 있는 Fast API 버전은: .

잘 부탁드립니다 :)


갱신하다

여러 가지 답이 있는데, 여기 계신 모든 독자와 작가님들께 감사드립니다. 나는 내 애플리케이션에서 이 솔루션을 다시 검토하고 있었다. 이제 보니 설정이 필요했습니다. 기본값은 입니다. 하지만 설정은 다음과 같습니다

server = FastAPI(
    title=app_settings.PROJECT_NAME,
    version=app_settings.VERSION,
)

@iedmrc가 @Kavindu Dodanduwa의 답변에 대해 언급했을 때 나는 그것을 놓친 것 같다.




먼저 파이썬의 예외 기본 클래스에 익숙해지도록 초대합니다. 문서에서 읽을 수 있습니다

둘째, fastApi 기본 예외 무시 동작을 읽어보십시오

에서 파생된 예외 또는 자식 클래스를 수락한다는 점을 이해해야 합니다. 예를 들어, 파이썬의 하위 클래스는 자체적으로 의 하위 클래스로 구성되어 있다.

따라서 이 배경을 사용하여 사용 가능한 예외를 설계하거나 자신만의 예외를 설계를 수행해야 합니다. 자세한 필드가 없거나 올바른 로거 구성이 없는 것이 로거에 문제가 있는 것 같습니다.

샘플 코드:

@app.get("/")
def read_root(response: Response):
    raise ArithmeticError("Divide by zero")


@app.exception_handler(Exception)
async def validation_exception_handler(request, exc):
    print(str(exc))
    return PlainTextResponse("Something went wrong", status_code=400)

출력:

추가 입력 및 응답




처리되지 않은 예외(내부 서버 오류)를 모두 캡처하려는 경우 매우 간단한 방법이 있습니다.

from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import Response
from traceback import print_exception

app = FastAPI()

async def catch_exceptions_middleware(request: Request, call_next):
    try:
        return await call_next(request)
    except Exception:
        # you probably want some kind of logging here
        print_exception(e)
        return Response("Internal server error", status_code=500)

app.middleware('http')(catch_exceptions_middleware)

이 미들웨어를 다른 모든 것보다 먼저 배치해야 합니다.




이런 거 할 수 있어요. 사용자 지정 오류 메시지와 함께 json 개체를 반환해야 하며 디버거 모드에서도 작동합니다.

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(Exception)
async def validation_exception_handler(request, err):
    base_error_message = f"Failed to execute: {request.method}: {request.url}"
    # Change here to LOGGER
    return JSONResponse(status_code=400, content={"message": f"{base_error_message}. Detail: {err}"})



이것은 Fastapi와 Starlette에서 알려진 문제이다.

나는 스타렛을 잡으려고 노력하고 있다다음과 같은 간단한 샘플을 통해 전체적으로 HTTP 예외가 발생합니다.

import uvicorn

from fastapi import FastAPI
from starlette.requests import Request
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def exception_callback(request: Request, exc: Exception):
    print("test")
    return JSONResponse({"detail": "test_error"}, status_code=500)


if __name__ == "__main__":
    uvicorn.run("test:app", host="0.0.0.0", port=1111, reload=True)


그건 효과가 있다. 브라우저를 열고 / 엔드포인트를 호출하고 액세스를 시도하면 HTTP 코드 "500 내부 서버 오류"가 포함된 json {"detail":"test_error"}이(가) 반환됩니다.

500 on browser 500 in IDE

하지만, 내가 스타렛만 바꿨을 때@app.exception_handler의 HTTP 예외,

import uvicorn

from fastapi import FastAPI
from starlette.requests import Request
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse

app = FastAPI()


@app.exception_handler(Exception)
async def exception_callback(request: Request, exc: Exception):
    print("test")
    return JSONResponse({"detail": "test_error"}, status_code=500)


if __name__ == "__main__":
    uvicorn.run("test:app", host="0.0.0.0", port=1111, reload=True)

exception_callback 메서드가 Starlette를 캡처할 수 없습니다에 액세스할 때 HTTP 예외가 발생했습니다. 그것은 404 오류를 보고했다.

404 on browser 404 in IDE

예외 동작은 다음과 같아야 합니다Starlette 때문에 예외로 장식된 메서드 exception_handler에서 HTTP Exception 오류를 캡처할 수 있습니다HTTP Exception은 Exception의 하위 클래스입니다.

그러나 Fastapi와 Starlette에서 보고된 알려진 문제입니다

그래서 우리는 현재 목표를 달성할 수 없습니다.




나는 미들웨어를 사용하여 "ASGI application_의 예외" 없이 예외를 잡는 방법을 찾았다. 이것이 다른 부작용이 있을지 모르겠지만 나에게는 잘 작동해! @iedmrc

@app.middleware("http")
async def exception_handling(request: Request, call_next):
    try:
        return await call_next(request)
    except Exception as exc:
        log.error("Do some logging here")
        return JSONResponse(status_code=500, content="some content")



사용자 정의를 추가하여 글로벌 예외를 처리할 수도 있습니다. 이 접근 방식의 장점은 사용자 지정 경로에서 http 예외가 발생하면 기본 Starlette의 오류 처리기에 의해 처리된다는 것입니다:

from typing import Callable

from fastapi import Request, Response, HTTPException, APIRouter, FastAPI
from fastapi.routing import APIRoute
from .logging import logger


class RouteErrorHandler(APIRoute):
    """Custom APIRoute that handles application errors and exceptions"""

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

        async def custom_route_handler(request: Request) -> Response:
            try:
                return await original_route_handler(request)
            except Exception as ex:
                if isinstance(ex, HTTPException):
                    raise ex
                logger.exception("uncaught error")
                # wrap error into pretty 500 exception
                raise HTTPException(status_code=500, detail=str(ex))

        return custom_route_handler


router = APIRouter(route_class=RouteErrorHandler)

app = FastAPI()
app.include_router(router)

fastapi==0.68.1로 작업했습니다.

사용자 지정 경로에 대한 추가 정보:




나는 내가 찾아서 구현한 429개의 상태 코드에 대한 사용자 지정 메시지를 제공하기 위한 fast api에 대한 글로벌 핸들러를 찾고 있었고, 나를 위해 잘 작동했다. @app.exception_handler(429) 비동기 defratelimit_handler(요청: 요청, exc: 예외): 반환 JSONResponse({'message': "당신의 요청 할당량을 초과했습니다. 잠시 후 시도하십시오." 'status': 'failed'})




하위 클래스를 지정한 다음 재정의합니다.

이 대답은 다음 방법을 읽고 영감을 받았습니다:

예:

class GenericExceptionMiddleware(ExceptionMiddleware):

    # Intentional: Defer __init__(...) to super class ExceptionMiddleware

    # @Override(ExceptionMiddleware)
    def _lookup_exception_handler(
            self, exc: Exception
    ) -> Optional[Callable]:
        if isinstance(exc, HTTPException):
            return self.__http_exception_handler
        else:
            return self.__exception_handler

    @classmethod
    async def __http_exception_handler(cls, request: fastapi.Request,  # @Debug
                                       ex: HTTPException):

        log.error("Unexpected error", cause=ex)
        resp = PlainTextResponse(content=f"Unexpected error: {ex.detail}"
                                         f"\n"
                                         f"\nException stack trace"
                                         f"\n====================="
                                         f"\n{ex}", # Improve to add full stack trace
                                 status_code=ex.status_code)
        return resp

    @classmethod
    async def __exception_handler(cls, request: fastapi.Request,  # @Debug
                                  ex: Exception):

        log.error("Unexpected error", cause=ex)
        resp = PlainTextResponse(content=f"Unexpected error: {ex}"
                                         f"\n"
                                         f"\nException stack trace"
                                         f"\n====================="
                                         f"\n{ex}", # Improve to add full stack trace
                                 status_code=fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR)
        return resp

샘플 사용량:

fast_api = FastAPI()
fast_api.add_middleware(GenericExceptionMiddleware, debug=fast_api.debug)

반응형