汉字谜盒-程序优化

汉字谜盒-程序优化

2026年9月20日·#编程学习/python学习笔记Python/Web开发·1360 字 7 分钟
浏览量加载中...
AI 摘要

把 print 换成 logging 模块(级别、格式、开关)并用 FastAPI 的统一异常处理器兜住所有报错

为什么要用 logging 而不是 print#

项目里到处用 print() 记录过程信息,会带来三个问题:

  1. 说不清什么时间、什么位置输出的——终端里只有一行文字,排错时很难定位
  2. 想关掉这些日志怎么办? 得把每一行 print 找出来删掉或注释掉
  3. 没法按重要性过滤——调试信息、正常信息、错误信息混在一起

用 logging 模块#

from fastapi import FastAPI
from fastapi.responses import FileResponse
import 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 < FATAL

basicConfig(level=...) 设成哪个级别,就只输出该级别及以上的日志。比如设 level=logging.ERROR,那些 logging.info(...) 就都不会打印了——一行配置就关掉了所有低级别日志,这正是 logging 比 print 强的地方。

级别用途
DEBUG调试细节(开发时用)
INFO正常运行的记录
WARNING警告(还能跑,但要注意)
ERROR出错了
FATAL致命错误

统一异常处理#

项目功能多,不可能在每个接口里都写 try/except。FastAPI 提供了统一异常处理器

from starlette.requests import Request
from 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}
Note

处理器里返回的 JSONResponse 没有设置 HTTP 状态码,所以浏览器/客户端拿到的 HTTP 状态码还是 200,只有响应体里的 code 是 500。这是本项目的约定(统一响应体里的业务码);如果想让 HTTP 状态码也是 500,写成 JSONResponse(status_code=500, content={...}) 即可。

Tip

统一异常处理的价值:接口报错时不再返回一大段 Python 堆栈,而是返回和正常响应同样格式的 JSON。前端只要判断 code 就能优雅提示用户,日志里也留下了具体是哪个路径、什么异常。

相关#

练习题#

一、回忆填空(写完再展开对答案)#

  1. 用 print 记录日志的问题:说不清 ____ 和 ____、想关掉要一行行删、无法按重要性 ____
  2. 配置日志:logging.____(level=..., format=...)
  3. format 里 %(asctime)s 是 ____、%(levelname)s 是 ____、%(filename)s 是 ____、%(lineno)d 是 ____
  4. 日志级别从低到高:DEBUG、、WARNING、、FATAL;设置 level 后只输出该级别 ____ 的日志
  5. 注册统一异常处理器:@app.____(Exception),函数形参一般是 request: ____exc: ____
  6. 异常处理器返回的对象类型必须是 ____,所以用 ____ 返回 JSON
填空答案(做完再点开)
  1. 时间 / 位置 / 过滤 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.ERROR vs level=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 logging
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import JSONResponse
from 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)

评论区

[ 标签 ]
# AI37# AI 编程2# AI工具1# Ajax2# Apifox1# AstrBot3# Astro2# CC Switch1# CDN2# Claude Code1# claudecode2# ClaudeCode1# Cloudflare2# CloudFlare2# CloudFlare-ImgBed3# coc3# CSS6# DeepSeek6# deepseek2# DELETE1# Docker1# EdgeOne3# Gist1# git1# GitHub1# hexo-circle-of-friends1# HTML6# HTTP5# ImageManager1# Java23# java13# JavaScript5# JDBC3# JSON2# JUnit1# LangChain25# Logback1# Maven6# Muse Spark1# Mybatis1# MyBatis4# MySQL28# MySql1# NapCat1# Node.js1# obsidian2# Obsidian5# OpenCode4# ORM1# PathVariable1# PicGo1# PyCharm1# Python65# RequestBody1# RequestMapping1# RESTful风格1# skills1# Slf4j1# SpringBoot11# SQL2# Streamlit5# Svelte2# TailwindCSS1# Telegram3# Tlias2# Vercel1# vscode2# Vue7# Waline3# WebDAV1# Web基础6# Web开发6# WinSCP1# YAML1# 三层架构1# 中二宣言1# 书籍1# 使用文档10# 写作1# 函数2# 刷步数1# 前端32# 动态1# 动漫1# 包1# 单词2# 博客7# 博客工作流1# 博客开发2# 参数接收1# 友链1# 反思2# 图床6# 地图1# 备份2# 大模型1# 奇思妙想1# 存储1# 学习方法6# 学校1# 宝塔面板3# 宝宝10# 对象1# 导航栏1# 工具2# 开发1# 开发工具1# 开发规范1# 开心1# 异常处理1# 影视2# 微信1# 性能优化2# 总结1# 想法15# 感受1# 感悟11# 指南1# 提示词工程1# 插件5# 故障排除1# 效率工具2# 教程10# 数据分析9# 数据库27# 数据结构1# 文件操作2# 斩神1# 日常92# 日志框架1# 朋友圈1# 朱元璋1# 模块1# 模板1# 正则表达式2# 测试1# 游戏2# 爬虫7# 生活迁移1# 电影2# 电脑1# 碎碎念1# 视觉识别1# 类1# 类型注解1# 网络基础2# 网络教室1# 羊毛2# 脚本2# 脚本工具1# 自动化2# 蓝奏云1# 订阅推荐2# 记录2# 评论系统1# 词根1# 词缀1# 说说1# 足迹1# 跑步2# 路径参数1# 转载2# 运动1# 部落冲突1# 配置1# 随机图1# 面向对象5# 音乐3# 音标1# 饮食1# 驼峰命名1# 高德地图1
[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

暂未播放

0:000:00
暂无歌词
找不到相关结果。
[ contents ]
[ 全部文章 ]