news 2026/4/3 4:34:06

FastAPI 异常处理最佳实践:这套代码模板让你不再 996

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI 异常处理最佳实践:这套代码模板让你不再 996

开篇:一个让人抓狂的下午

“接口挂了,返回 500。”

看到这条消息,你打开服务器日志,心想:来吧,看看是什么妖魔鬼怪。

2024-01-15 14:30:00 | INFO | 应用启动完成 2024-01-15 14:30:05 | INFO | 收到请求: POST /api/generate

然后……就没了。

没有错误信息,没有堆栈跟踪,什么都没有。

Bug 像个忍者,来无影去无踪。

你开始加 print,部署,再加 print,再部署……三小时后,终于发现是某个变量是 None。

三个小时,就为了找一个空指针。

罪魁祸首?翻开代码,你看到了这行:

try:result=some_function()exceptException:pass# 就是这行!

某位前人写的代码,捕获了所有异常,然后——什么都不做。

异常就这么被"吞"掉了,悄无声息,像被灭霸打了个响指。

如果你也经历过这种绝望,这篇文章就是为你准备的。


异常处理的"三道防线"

好的异常处理就像洋葱——一层一层的(而且可能让你流泪)。

抛出异常
业务异常
未知异常
HTTP 请求
第一道防线: 全局异常处理器
第二道防线: 业务异常处理
第三道防线: 端点级 try-except
业务逻辑
异常类型?
返回友好错误提示
记录日志 + 返回通用错误

三道防线,各司其职

防线职责比喻
全局异常处理器兜底所有漏网之鱼最后一道城墙
业务异常处理处理"意料之中"的错误前线哨兵
端点级 try-except精细化异常恢复贴身保镖

记住:异常不会消失,只会被藏起来。我们的目标是:让每个异常都无处可藏。


第一步:给异常办个"身份证"

裸奔的raise Exception("出错了")是不够的。我们需要给异常分门别类:

# app/core/exceptions.pyfromtypingimportOptional,AnyclassAppException(Exception):"""应用异常基类 —— 所有业务异常的祖宗"""def__init__(self,message:str,code:str="UNKNOWN_ERROR",status_code:int=500,details:Optional[Any]=None):self.message=message self.code=code# 错误码,前端靠这个判断self.status_code=status_code# HTTP 状态码self.details=details# 额外信息,想塞啥塞啥super().__init__(message)classValidationError(AppException):"""参数不对?400 伺候"""def__init__(self,message:str,details:Any=None):super().__init__(message,"VALIDATION_ERROR",400,details)classNotFoundError(AppException):"""找不到?404 安排"""def__init__(self,resource:str,resource_id:str):super().__init__(f"{resource}not found:{resource_id}","NOT_FOUND",404,{"resource":resource,"id":resource_id})classGenerationError(AppException):"""AI 罢工了"""def__init__(self,message:str,model:str=None):super().__init__(message,"GENERATION_ERROR",500,{"model":model})classExternalServiceError(AppException):"""第三方服务挂了,锅不在我"""def__init__(self,service:str,message:str):super().__init__(f"{service}error:{message}","EXTERNAL_SERVICE_ERROR",502,# 502 = 上游挂了{"service":service})

有了这套"身份证"系统,每个异常都有:

  • 错误码:前端可以根据 code 显示不同的提示
  • 状态码:HTTP 语义正确,运维监控不会瞎报警
  • 详情:调试时的救命稻草

第二步:设立"全局关卡"

接下来,在 FastAPI 里注册异常处理器。把它想象成机场安检——每个异常都得过这一道:

# app/main.pyfromfastapiimportFastAPI,Requestfromfastapi.responsesimportJSONResponsefromapp.core.exceptionsimportAppExceptionfromapp.core.loggerimportloggerimporttraceback app=FastAPI()@app.exception_handler(AppException)asyncdefapp_exception_handler(request:Request,exc:AppException):"""业务异常处理器 —— 处理"可预期的坏消息" """logger.warning(f"业务异常 [{exc.code}]:{exc.message}",extra={"path":request.url.path,"method":request.method,"code":exc.code,"details":exc.details})returnJSONResponse(status_code=exc.status_code,content={"success":False,"error":{"code":exc.code,"message":exc.message,"details":exc.details}})@app.exception_handler(Exception)asyncdefglobal_exception_handler(request:Request,exc:Exception):"""全局异常处理器 —— 最后一道城墙,专治各种"意外惊喜" """error_detail={"code":"INTERNAL_ERROR","message":"An internal error occurred. Please try again later."}# 开发环境?把底裤都给你看ifsettings.DEBUG:error_detail["message"]=str(exc)error_detail["type"]=type(exc).__name__ error_detail["traceback"]=traceback.format_exc().split("\n")# 不管什么环境,日志里必须有完整信息logger.error(f"未处理的异常:{type(exc).__name__}:{str(exc)}",extra={"path":request.url.path,"method":request.method,"traceback":traceback.format_exc()# 完整堆栈,一个字都不能少!})returnJSONResponse(status_code=500,content={"success":False,"error":error_detail})

划重点

  • AppExceptionWARNING级别——这是"意料之中"的错误
  • 未知ExceptionERROR级别——这是"意料之外"的惊喜
  • traceback.format_exc()是你的好朋友,完整堆栈一览无余
  • 生产环境别暴露内部错误,不然黑客会感谢你的坦诚

第三步:端点级"精细作战"

全局处理器是最后防线,但有些异常需要在端点级别就地解决:

# app/api/endpoints/generate.py@router.post("/shot-image")asyncdefgenerate_shot_image(request:GenerateShotImageRequest):"""生成分镜图片 —— 一个充满意外的端点"""# 参数校验:先礼后兵ifnotrequest.prompt.strip():raiseValidationError("Prompt cannot be empty")try:adapter=ImageGenerationAdapterFactory.get_current_adapter()result=awaitadapter.generate_shot_image(prompt=request.prompt,width=request.width,height=request.height)ifnotresult.get("success"):raiseGenerationError(result.get("error","Unknown generation error"),model=settings.IMAGE_MODEL)return{"success":True,"data":result}exceptGenerationError:raise# 已经是业务异常,放它走excepttorch.cuda.OutOfMemoryError:# 显存爆了?给个人话提示raiseGenerationError("GPU out of memory. Please try a smaller image size.")exceptExceptionase:# 未知异常:先记录,再转换logger.error(f"Unexpected error in generate_shot_image:{e}")raiseGenerationError(f"Generation failed:{str(e)}")

异常处理策略表

情况处理方式理由
已是业务异常直接raise已经有身份证了
已知特定异常转换为业务异常给它办个身份证
未知异常记日志 + 转换先留案底,再处理

第四步:日志配置——"案发现场"的监控探头

推荐 loguru,比标准库的 logging 好用一万倍(真的):

# app/core/logger.pyfromloguruimportloggerimportsys logger.remove()# 先清场# 控制台输出:花里胡哨但好用logger.add(sys.stdout,format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | ""<level>{level: <8}</level> | ""<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | ""<level>{message}</level>",level="DEBUG")# 文件日志:朴实无华但可靠logger.add("logs/app_{time:YYYY-MM-DD}.log",format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} | {message}",level="INFO",rotation="00:00",# 每天零点轮转retention="30 days",# 保留 30 天compression="zip"# 自动压缩,省空间)# 错误日志:单独伺候,VIP 待遇logger.add("logs/error_{time:YYYY-MM-DD}.log",format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} | {message}\n{exception}",level="ERROR",rotation="00:00",retention="90 days"# 错误日志多留几天,翻旧账用得上)

第五步:请求追踪——给每个请求一个"案件编号"

importtimeimportuuid@app.middleware("http")asyncdeflog_requests(request:Request,call_next):"""请求日志中间件 —— 来了就得登记"""request_id=str(uuid.uuid4())[:8]# 8 位够用了logger.info(f"[{request_id}] -->{request.method}{request.url.path}")start=time.time()response=awaitcall_next(request)duration=time.time()-start logger.info(f"[{request_id}] <--{response.status_code}in{duration:.2f}s")# 响应头里也带上,方便前端response.headers["X-Request-ID"]=request_idreturnresponse

效果展示

2024-01-15 14:30:00 | INFO | [a1b2c3d4] --> POST /api/generate/shot-image 2024-01-15 14:30:12 | INFO | [a1b2c3d4] <-- 200 in 12.34s

前端说"接口报错了",你只需要问一句:“Request ID 多少?”

然后grep a1b2c3d4 logs/error_*.log,破案。


红线:绝对禁止的写法

立个规矩,刻在 DNA 里:

# 禁止!禁止!禁止!try:something()exceptException:pass# 这是在犯罪

这种代码的危害:

  1. 异常凭空消失,debug 时怀疑人生
  2. 埋下定时炸弹,不知道什么时候爆炸
  3. 让接手的同事想打人

正确姿势

# 姿势一:只捕获特定异常try:result=maybe_fail()exceptFileNotFoundError:result=default_value# 这个异常我能处理exceptPermissionErrorase:logger.warning(f"权限不足:{e}")raise# 这个我处理不了,抛出去# 姿势二:实在要忽略,至少留个遗言try:optional_operation()exceptSomeSpecificErrorase:logger.debug(f"忽略的异常(有意为之):{e}")

快速抄作业清单

原则做法
不吞异常except: pass写一次,绩效扣一分
分层处理全局 → 业务 → 端点,层层设防
日志完整堆栈、请求 ID、路径,一个都不能少
错误码标准化定义业务异常类,别裸抛 Exception
环境区分开发给详情,生产给脸色
请求可追踪X-Request-ID,追凶利器

总结:异常处理三原则

把这三句话贴在工位上:

  1. 要么处理它——你知道怎么应对这个异常
  2. 要么记录它——你不知道怎么处理,但要留下证据
  3. 要么抛出它——让更上层的人来处理

但绝不能忽视它。

有了这套体系,下次接口报 500,你只需要:

  1. 拿到 Request ID
  2. 搜索错误日志
  3. 看完整堆栈
  4. 定位问题

整个过程,一分钟

再也不用三小时排查一个空指针了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/2 9:04:38

用AI快速开发@requestmapping应用

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个requestmapping应用&#xff0c;利用快马平台的AI辅助功能&#xff0c;展示智能代码生成和优化。点击项目生成按钮&#xff0c;等待项目生成完整后预览效果 在Java Web开发…

作者头像 李华
网站建设 2026/3/26 18:19:47

5分钟用Cursor搭建Java环境测试沙盒

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个Java沙盒环境生成器&#xff0c;能够快速创建隔离的临时Java运行环境&#xff0c;包含可选的JDK版本和常用库。功能要求&#xff1a;1) 基于Docker的轻量级隔离环境 2) 预装…

作者头像 李华
网站建设 2026/3/26 21:52:02

告别手动处理:Adobe弹窗自动化解决方案

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 设计一个自动化脚本&#xff0c;能够在不影响用户工作的情况下静默处理Adobe Genuine Service Alert弹窗。要求&#xff1a;1. 完全后台运行&#xff1b;2. 处理速度快于手动操作&a…

作者头像 李华
网站建设 2026/4/3 0:21:31

用AI快速开发java调用python应用

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个java调用python应用&#xff0c;利用快马平台的AI辅助功能&#xff0c;展示智能代码生成和优化。点击项目生成按钮&#xff0c;等待项目生成完整后预览效果 为什么需要Java…

作者头像 李华
网站建设 2026/4/3 3:40:39

企业级Oracle运维:ORA-01033实战处理案例

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个Oracle数据库运维案例展示应用&#xff0c;包含&#xff1a;1. 3个典型ORA-01033错误场景&#xff1b;2. 分步骤解决方案演示&#xff1b;3. 预防措施建议&#xff1b;4. 互…

作者头像 李华
网站建设 2026/3/28 6:39:40

开发者好帮手-发票查验接口C#代码示例

现如今&#xff0c;随着发票使用率的普及&#xff0c;发票真伪难辨、重复报销、虚假入账等问题&#xff0c;依然是困扰企业财务合规与效率提升的“顽疾”。如何快速、准确、批量地验证发票真伪&#xff0c;构建财税风险的第一道防线&#xff1f;答案就在-发票查验接口。 一、告…

作者头像 李华