Quick Answer

FastAPI converts unhandled exceptions in a route into a 500 response internally, so they never reach sys.excepthook — a generic monitoring SDK sees nothing. The fix is ASGI middleware that wraps every request, observes exceptions as they pass through, reports them, then re-raises so FastAPI's own error handling still runs unchanged. With Ravn this is app.add_middleware(RavnMiddleware) after ravn.init().

FastAPI apps have a specific blind spot for error monitoring: because Starlette (the ASGI toolkit FastAPI is built on) resolves unhandled exceptions into HTTP responses before they can reach the interpreter's top-level exception hook, any tool relying purely on sys.excepthook will report zero errors from a FastAPI service, no matter how often it 500s.

Why doesn't sys.excepthook catch FastAPI exceptions?

Each request in an ASGI app runs inside its own coroutine, managed by the ASGI server (uvicorn, hypercorn, etc.) and Starlette's routing layer. When a route handler raises, Starlette's exception middleware catches it, maps it to a response (a 500 by default, or a custom handler if you registered one), and sends that response. The exception's lifecycle ends there — it never propagates to the process-level exception hook, because from the interpreter's point of view, nothing went uncaught.

The correct integration point is the ASGI middleware stack itself, where you can observe the exception before Starlette converts it into a response.

How do you capture FastAPI exceptions with Ravn?

import ravn
from ravn.integrations.fastapi import RavnMiddleware
from fastapi import FastAPI

app = FastAPI()

ravn.init(api_key="your_api_key")
app.add_middleware(RavnMiddleware)

# Every unhandled exception in any route is now captured,
# with the request path and method attached automatically.

Under the hood, the middleware is a small wrapper around call_next:

class RavnMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        try:
            return await call_next(request)
        except Exception as exc:
            capture_exception(exc, metadata={
                "mechanism": "fastapi",
                "path": request.url.path,
                "method": request.method,
            })
            raise  # FastAPI's own error handling still runs unchanged

The raise at the end matters: monitoring is purely observational here. Your API's status codes and error response bodies are exactly what they'd be without the middleware installed.

Does this catch exceptions inside custom exception handlers?

Mostly, but with one caveat worth knowing. If you register a handler with @app.exception_handler(SomeException), FastAPI can resolve that exception before it reaches outer middleware, depending on your Starlette version and middleware ordering — meaning RavnMiddleware never sees it, because as far as the middleware stack is concerned, nothing was unhandled. If you rely on custom exception handlers for specific error types, call ravn.capture_exception(exc) explicitly inside those handlers to make sure they're still recorded.

Does the middleware add latency to every request?

Not meaningfully on the success path. RavnMiddleware only does anything when call_next raises — for the overwhelming majority of requests that complete normally, it's a pass-through await with no additional work. On the exception path, reporting happens through a background thread pool, not inline, so it doesn't add to your response latency even when an error does occur.

What about Starlette apps without FastAPI?

RavnMiddleware is built directly on Starlette's BaseHTTPMiddleware, so it works the same way in a plain Starlette app — FastAPI isn't a requirement, just the more common case.

For framework-agnostic background context, see the Python error monitoring guide, or compare against a full observability platform in the Sentry alternative breakdown.

Add error monitoring to your FastAPI app in two lines.

ASGI middleware, AI root-cause analysis, free to start.

Get Started Free

Frequently asked questions

Does FastAPI report unhandled exceptions automatically?

No. FastAPI converts unhandled exceptions into a 500 response internally, so they never reach sys.excepthook. A tool that only hooks the global exception handler won't see errors raised inside your endpoints.

How do I capture FastAPI endpoint exceptions?

Add ASGI middleware that wraps every request, catches exceptions as they pass through, reports them, and re-raises. With Ravn this is app.add_middleware(RavnMiddleware) after ravn.init().

Does the middleware change my API's error responses?

No. The middleware re-raises every exception after reporting it, so FastAPI's exception handlers, status codes, and response bodies are unaffected.

Does this work with custom exception handlers?

Mostly, with one caveat: an @app.exception_handler() for a specific type can resolve the exception before it reaches outer middleware, so it won't propagate through RavnMiddleware. Report those explicitly with ravn.capture_exception() inside the handler.

Does this slow down request handling?

Not meaningfully. The middleware only does work on the exception path, and reporting happens on a background thread. On the success path — the vast majority of requests — it adds effectively nothing.