FastAPI部署Cloud Run:如何让Cloud Error Reporting仅展示业务异常
解决FastAPI部署Cloud Run时日志显示无关框架异常的问题
核心原因
当业务代码抛出异常后,若客户端提前断开连接(比如用户刷新页面、请求中断),Uvicorn/AnyIO会触发EndOfStream等框架级异常,导致日志中出现嵌套异常提示During handling of the above exception, another exception occurred:,掩盖了实际的业务异常。
解决方案
1. 自定义日志过滤器,屏蔽框架无关异常
给Uvicorn添加日志过滤器,过滤掉AnyIO、Starlette等框架抛出的非业务异常,只保留业务代码的异常信息。
示例代码:
import logging import os from typing import Any class CloudRunLogFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: # 过滤AnyIO的EndOfStream异常 if "EndOfStream" in str(record.msg) or "anyio" in str(record.pathname): return False # 过滤Starlette/Uvicorn的内部非业务异常 if "starlette" in str(record.pathname) and "During handling of the above exception" not in str(record.msg): return False # 保留业务相关异常日志 return True # 启动FastAPI服务时配置日志过滤器 if __name__ == "__main__": import uvicorn uvicorn_logger = logging.getLogger("uvicorn") uvicorn_logger.addFilter(CloudRunLogFilter()) uvicorn.run("main:app", host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
2. 优化全局异常处理器,避免框架二次异常
确保自定义异常处理器能正确捕获所有业务异常,返回合法HTTP响应,防止框架因响应生成失败触发额外异常。
示例代码:
from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse import logging app = FastAPI() # 自定义业务异常类 class BusinessException(Exception): def __init__(self, message: str, status_code: int = 400): self.message = message self.status_code = status_code # 全局异常处理器 @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): if isinstance(exc, BusinessException): return JSONResponse( status_code=exc.status_code, content={"detail": exc.message} ) elif isinstance(exc, HTTPException): return JSONResponse( status_code=exc.status_code, content={"detail": exc.detail} ) else: # 仅记录核心异常信息,关闭exc_info避免嵌套日志 logging.error(f"Unexpected error: {str(exc)}", exc_info=False) return JSONResponse( status_code=500, content={"detail": "Internal server error"} )
3. 调整Uvicorn日志配置
降低日志级别并简化格式,减少框架调试日志输出,聚焦业务异常。
示例启动配置:
uvicorn.run( "main:app", host="0.0.0.0", port=int(os.environ.get("PORT", 8080)), log_level="info", log_config={ "version": 1, "disable_existing_loggers": False, "formatters": { "default": { "format": "%(levelname)s: %(message)s" } }, "handlers": { "default": { "formatter": "default", "class": "logging.StreamHandler" } }, "loggers": { "uvicorn": {"handlers": ["default"], "level": "INFO"} } } )
关键注意事项
- 过滤规则需根据实际日志内容微调,避免误过滤有用的业务日志。
- 全局异常处理器中使用
exc_info=False,防止触发框架的嵌套异常日志。 - Cloud Run会收集容器标准输出,确保Uvicorn日志直接输出到stdout/stderr即可。
内容的提问来源于stack exchange,提问作者Jasper Madrone
相关产品推荐
相关产品推荐

