FastAPI子模块捕获异常时如何记录完整请求对象?
FastAPI底层异常处理后记录顶层请求的优化方案
场景复现
你的FastAPI端点代码:
@router.post("/my_test") async def post_my_test(request: Request): a = do_stuff(request.var1) b = do_different_stuff(request.var2) return a + b
底层业务逻辑中,calc_it会捕获异常并返回回退值:
async def calc_it(m, n): try: return m/n except ZeroDivisionError: logging.exception("Error occurred") return -1
现有三种方案的痛点:
- 方案1:逐层传递异常信息到顶层记录,代码冗余,破坏业务逻辑简洁性
- 方案2:逐层传递请求对象,无关函数被迫接收参数,维护成本高
- 方案3:常规中间件/异常处理器无法捕获已被底层处理的异常
优化方案
方案一:用ContextVars传递请求标记(轻量无侵入)
ContextVars是Python为异步/多线程场景设计的上下文变量,天然隔离不同请求的上下文,不会出现异步任务标记串用的问题。
- 定义标记变量
from contextvars import ContextVar need_log_request = ContextVar("need_log_request", default=False)
- 底层异常处设置标记
async def calc_it(m, n): try: return m/n except ZeroDivisionError: logging.exception("Error occurred") # 标记当前请求需要记录 need_log_request.set(True) return -1
- 顶层端点统一处理记录
@router.post("/my_test") async def post_my_test(request: Request): a = await do_stuff(request.var1) b = await do_different_stuff(request.var2) # 检查标记并记录请求 if need_log_request.get(): logging.info(f"Request to /my_test: {await request.json()}") # 可选:重置标记,避免跨上下文污染(ContextVars默认隔离,可不加) need_log_request.set(False) return a + b
优势:无需传递请求对象,仅用布尔标记实现逻辑分离,代码无冗余。
方案二:用ContextVars传递请求对象(直接高效)
你担心传递大对象是反模式,但ContextVars存储的是对象引用,不会拷贝整个请求,性能开销可忽略;且FastAPI的Request对象本身就是请求上下文内的单例,传递引用完全合理。
- 定义请求上下文变量
from contextvars import ContextVar from fastapi import Request current_request = ContextVar("current_request")
- 中间件自动注入请求对象
@app.middleware("http") async def set_current_request(request: Request, call_next): current_request.set(request) response = await call_next(request) return response
- 底层直接获取请求并记录
async def calc_it(m, n): try: return m/n except ZeroDivisionError: request = current_request.get() logging.exception(f"Error occurred with request: {await request.json()}") return -1
优势:业务代码完全无需处理请求传递,中间件自动完成注入,底层可随时获取完整请求信息。
总结
两种方案均能解决代码冗余问题:方案一更轻量,适合仅需标记触发顶层记录的场景;方案二更直接,适合底层需要直接使用请求信息的场景。
内容的提问来源于stack exchange,提问作者fragant
相关产品推荐
相关产品推荐

