Exceptions with handlers are replaced in a background task
See original GitHub issueWhen a background task raises an exception that has a FastAPI handler, that exception is replaced by FastAPI and the original error cannot be found in the server logs. This makes debugging and investigations incredibly hard as it’s impossible to know where the error occurred.
FastAPI should preserve the original exception so that it can be logged and investigated server-side.
Example
from fastapi import FastAPI, Request, BackgroundTasks
from fastapi.responses import JSONResponse
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
app = FastAPI()
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
)
async def process_unicorn(name: str):
if name == "yolo":
raise UnicornException(name=name)
# else do_something(name)
@app.get("/unicorns/{name}")
async def sync_process_unicorn(name: str, background_tasks: BackgroundTasks):
await process_unicorn(name)
return name
@app.get("/unicorns_async/{name}")
async def async_process_unicorn(name: str, background_tasks: BackgroundTasks):
"""Accept the unicorn and process it in the background"""
background_tasks.add_task(process_unicorn, name=name)
return f"Processing {name} asynchronously."
Description
/unicorns/yolo
correctly returns an error response/unicorns_async/yolo
returns the correct response to the client. I expect error logs and traceback of aUnicornException
on the server. However, that exception is replaced with one that FastAPI generates, without context.
INFO: 127.0.0.1:32848 - "GET /unicorns_async/yolo HTTP/1.1" 200 OK
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/luyao/.local/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/luyao/.local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/home/luyao/.local/lib/python3.8/site-packages/fastapi/applications.py", line 190, in __call__
await super().__call__(scope, receive, send)
File "/home/luyao/.local/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/luyao/.local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/home/luyao/.local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/home/luyao/.local/lib/python3.8/site-packages/starlette/exceptions.py", line 86, in __call__
raise RuntimeError(msg) from exc
RuntimeError: Caught handled exception, but response already started.
Environment
- OS: Linux (Ubuntu 20.10)
- FastAPI Version: 0.62.0
- Python version: occurs on both 3.8.6 and 3.7.9
Issue Analytics
- State:
- Created 3 years ago
- Comments:8 (3 by maintainers)
Top Results From Across the Web
Managing Exceptions in BackgroundService or ... - Mike Conrad
.Net 6 introduces a welcome change to exceptions which is detailed ... In this post, we will cover proper exception handling for hosted ......
Read more >How to Handle Exceptions from Background Worker ...
Exceptions are thrown on background worker thread's call stack and its own stack frames are unfolded.
Read more >NET 6 breaking change: Exception handling in hosting - .NET
NET 6 breaking change in core .NET libraries where unhandled exceptions from a BackgroundService are logged instead of lost.
Read more >How to add background tasks when request fails and ...
The way to do this is to override the HTTPException error handler, and since there is no BackgroundTasks object in the exception_handler ...
Read more >Background to exception handling in SQL Server
However, the examples demonstrated in preceding sections should not justify an absolute replacement of any T-SQL code that references RAISERROR ...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Hmm, you’re right that it seems to be an issue with Starlette. I’ll open an issue there instead.
I don’t think this particular issue has to do with forgetting to await the result or threading though. The exception is clearly there - in fact, if I go to the code quoted in the traceback and insert
print(exc)
before theraise
, it literally prints out the exact exception that the application has raised. So the original exception is available in the context; it’s just being replaced with a less helpful error instead of surfaced through the logs.@Matthieu-Tinycoaching
I solved this by using a message broker (e.g. Rabbit MQ) and not using BackGroundTasks. I needed a message broker for other reasons, so it was no extra work for me. However, that will probably be overkill to solve only this problem.
There is a good blog post here on how to mitigate this problem (I’m the author) as well as this library that can also solve this problem for you.