汉字谜盒-程序优化
汉字谜盒-程序优化
把 print 换成 logging 模块(级别、格式、开关)并用 FastAPI 的统一异常处理器兜住所有报错
为什么要用 logging 而不是 print
项目里到处用 print() 记录过程信息,会带来三个问题:
- 说不清什么时间、什么位置输出的——终端里只有一行文字,排错时很难定位
- 想关掉这些日志怎么办? 得把每一行
print找出来删掉或注释掉 - 没法按重要性过滤——调试信息、正常信息、错误信息混在一起
用 logging 模块
from fastapi import FastAPIfrom fastapi.responses import FileResponseimport logging
# 配置日志的基本信息logging.basicConfig( level=logging.INFO, # 日志级别 format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s")
app = FastAPI()
@app.get("/")def root(): logging.info("访问首页~") return FileResponse("static/index.html")format 里的占位符:
| 占位符 | 含义 |
|---|---|
%(asctime)s | 时间 |
%(levelname)s | 日志级别 |
%(filename)s | 文件名 |
%(lineno)d | 行号 |
%(message)s | 日志信息 |
输出的样子:
2026-09-20 15:04:12,301 - INFO - [main.py:29] - 访问首页~日志级别
日志级别是给日志信息贴上的”重要性标签”,常见级别从低到高:
DEBUG < INFO < WARNING < ERROR < FATALbasicConfig(level=...) 设成哪个级别,就只输出该级别及以上的日志。比如设 level=logging.ERROR,那些 logging.info(...) 就都不会打印了——一行配置就关掉了所有低级别日志,这正是 logging 比 print 强的地方。
| 级别 | 用途 |
|---|---|
| DEBUG | 调试细节(开发时用) |
| INFO | 正常运行的记录 |
| WARNING | 警告(还能跑,但要注意) |
| ERROR | 出错了 |
| FATAL | 致命错误 |
统一异常处理
项目功能多,不可能在每个接口里都写 try/except。FastAPI 提供了统一异常处理器:
from starlette.requests import Requestfrom starlette.responses import JSONResponse
@app.exception_handler(Exception)def handle_exception(request: Request, exc: Exception): logging.error(f"处理异常, 请求路径: {request.url}, 异常信息: {exc}") return JSONResponse(content={"code": 500, "message": "服务器内部错误, 请联系管理员~", "data": None})| 要点 | 说明 |
|---|---|
@app.exception_handler(Exception) | 注册”捕获所有异常”的处理器(Exception 是所有异常的父类) |
request: Request | 出事的那次请求(可以从里面拿到 request.url) |
exc: Exception | 捕获到的异常对象 |
JSONResponse(...) | 返回的对象类型必须是 Response,用 JSONResponse 返回 JSON |
处理前后的对比:
{"code": 200, "message": "AI回复成功", "data": "..."}{"code": 500, "message": "服务器内部异常", "data": null}处理器里返回的 JSONResponse 没有设置 HTTP 状态码,所以浏览器/客户端拿到的 HTTP 状态码还是 200,只有响应体里的 code 是 500。这是本项目的约定(统一响应体里的业务码);如果想让 HTTP 状态码也是 500,写成 JSONResponse(status_code=500, content={...}) 即可。
统一异常处理的价值:接口报错时不再返回一大段 Python 堆栈,而是返回和正常响应同样格式的 JSON。前端只要判断 code 就能优雅提示用户,日志里也留下了具体是哪个路径、什么异常。
相关
练习题
一、回忆填空(写完再展开对答案)
- 用 print 记录日志的问题:说不清 ____ 和 ____、想关掉要一行行删、无法按重要性 ____
- 配置日志:
logging.____(level=..., format=...) - format 里
%(asctime)s是 ____、%(levelname)s是 ____、%(filename)s是 ____、%(lineno)d是 ____ - 日志级别从低到高:DEBUG、、WARNING、、FATAL;设置
level后只输出该级别 ____ 的日志 - 注册统一异常处理器:
@app.____(Exception),函数形参一般是request: ____和exc: ____ - 异常处理器返回的对象类型必须是 ____,所以用
____返回 JSON
填空答案(做完再点开)
- 时间 / 位置 / 过滤 2. basicConfig 3. 时间 / 日志级别 / 文件名 / 行号 4. INFO、ERROR / 及以上 5. exception_handler / Request / Exception 6. Response / JSONResponse
二、裸写题
-
2-1 给接口加日志 写一个 FastAPI 应用,配置 logging(级别 INFO,格式含时间/级别/文件名行号/消息),在
/接口里用logging.info记录”访问首页”,用TestClient调用一次看输出。提示(先自己想,实在想不出再点开)一级 · 思路:先 basicConfig 一次(放在程序开头),之后到处用
logging.info/error二级 · 方法:logging.basicConfig(level=logging.INFO, format=...)三级 · 骨架:format 用"%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s" -
2-2 验证日志级别开关 把
level改成logging.ERROR再跑一次,观察logging.info还打不打印,解释原因。提示一级 · 思路:级别设高了,低级别日志就被过滤掉 二级 · 方法:
level=logging.ERRORvslevel=logging.INFO三级 · 骨架:INFO < ERROR,所以设 ERROR 后 INFO 不输出 -
2-3 统一异常处理 写一个会抛异常的接口(如
1/0),不加异常处理器时看看返回什么;再加上@app.exception_handler(Exception)观察返回的 JSON。提示一级 · 思路:对比”有/没有异常处理器”两次的响应 二级 · 方法:
@app.exception_handler(Exception)+JSONResponse三级 · 骨架:处理器里用logging.error记下request.url和异常信息
参考答案(做完再点开)
import loggingfrom fastapi import FastAPIfrom starlette.requests import Requestfrom starlette.responses import JSONResponsefrom fastapi.testclient import TestClient
# 2-1 配置日志logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s")
app = FastAPI(title="优化练习")
@app.get("/")def root(): logging.info("访问首页") return {"message": "ok"}
@app.get("/boom")def boom(): logging.info("准备触发异常") return {"value": 1 / 0} # 故意抛异常
# 2-3 统一异常处理器@app.exception_handler(Exception)def handle_exception(request: Request, exc: Exception): logging.error(f"处理异常, 请求路径: {request.url}, 异常信息: {exc}") return JSONResponse(content={"code": 500, "message": "服务器内部错误", "data": None})
if __name__ == "__main__": # TestClient 默认会把服务端异常直接抛出来(方便调试),关掉才能看到处理器的返回 client = TestClient(app, raise_server_exceptions=False) print(client.get("/").json()) print(client.get("/boom").json()) # 被异常处理器兜住,返回 {code:500,...}
# 2-2:把 level 改成 logging.ERROR 再跑,# 就会看到 "访问首页" 这类 INFO 日志不再输出(INFO 低于 ERROR)评论区
如果你喜欢,那么欢迎来到我的世界!
了解更多













