Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch `Exception` in fast api globally

I am trying to catch unhandled exceptions at global level. So somewhere in main.py file I have the below:

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

But the above method is never executed. However, if I write a custom exception and try to catch it (as shown below), it works just fine.

class MyException(Exception):
  #some code

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

I have gone through Catch exception type of Exception and process body request #575. But this bug talks about accessing request body. After seeing this bug, I feel it should be possible to catch Exception. FastAPI version I am using is: fastapi>=0.52.0.

Thanks in advance :)


Update

There are multiple answers, I am thankful to all the readers and authors here. I was revisiting this solution in my application. Now I see that I needed to set debug=False, default it's False, but I had it set to True in

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

It seems that I missed it when @iedmrc commented on answer given by @Kavindu Dodanduwa.

like image 986
Ajeet Singh Avatar asked May 04 '20 16:05

Ajeet Singh


People also ask

How do you handle exceptions globally?

The Controller Advice class to handle the exception globally is given below. We can define any Exception Handler methods in this class file. The Product Service API controller file is given below to update the Product. If the Product is not found, then it throws the ProductNotFoundException class.

How exceptions are handled globally in C#?

An ExceptionFilterAttribute is used to collect unhandled exceptions. You can register it as a global filter, and it will function as a global exception handler. Another option is to use a custom middleware designed to do nothing but catch unhandled exceptions.


4 Answers

In case you want to capture all unhandled exceptions (internal server error), there's a very simple way of doing it. Documentation

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

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
        return Response("Internal server error", status_code=500)

app.middleware('http')(catch_exceptions_middleware)

Make sure you place this middleware before everything else.

like image 121
AndreFeijo Avatar answered Oct 04 '22 18:10

AndreFeijo


Adding a custom APIRoute can be also be used to handle global exceptions. The advantage of this approach is that if a http exception is raised from the custom route it will be handled by default Starlette's error handlers:

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)

Worked for me with fastapi==0.68.1.

More on custom routes: https://fastapi.tiangolo.com/advanced/custom-request-and-route/

like image 39
madox2 Avatar answered Oct 04 '22 18:10

madox2


You can do something like this. It should return a json object with your custom error message also works in debugger mode.

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}"})
like image 6
Akihero3 Avatar answered Oct 04 '22 18:10

Akihero3


It is a known issue on the Fastapi and Starlette.

I am trying to capture the StarletteHTTPException globally by a following simple sample.

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)


It works. I open the browser and call the endpoint / and try to access http://127.0.0.1:1111/ , it will return the json {"detail":"test_error"} with HTTP code "500 Internal Server Error" .

500 on browser 500 in IDE

However, when I only changed StarletteHTTPException to Exception in the @app.exception_handler,

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)

The method exception_callback could not capture the StarletteHTTPException when I accessed the http://127.0.0.1:1111/ . It reported 404 error.

404 on browser 404 in IDE

The excepted behaviour should be: StarletteHTTPException error could be captured by the method exception_handler decorated by Exception because StarletteHTTPException is the child class of Exception.

However, it is a known issue reported in Fastapi and Starlette

  • https://github.com/tiangolo/fastapi/issues/2750
  • https://github.com/tiangolo/fastapi/issues/2683
  • https://github.com/encode/starlette/issues/1129

So we are not able to acheieve the goal currently.

like image 5
zhfkt Avatar answered Oct 04 '22 19:10

zhfkt