应使用 logging.exception() 记录异常堆栈,它自动附加 traceback 且保留异常上下文;配 RotatingFileHandler 限制日志大小与数量;自定义 Formatter 包含时间、级别、模块、行号;并在顶层 hook 中兜底捕获未处理异常。

用 logging.exception() 捕获并写入完整堆栈
遇到异常时,光靠 logging.error() 只能记下消息,不带 traceback;必须用 logging.exception() 才能自动附加当前异常的完整堆栈。它等价于 logging.error(..., exc_info=True),但更直观、不易漏掉参数。
常见错误是手动拼接 str(e) 或调用 traceback.format_exc() 再塞进 logging.error() —— 这样容易丢失原始异常上下文(比如 cause 链、__cause__ / __context__),也绕过了 logging 的 handler 和 formatter 控制。
实操建议:
- 只在
except块内调用logging.exception("关键操作失败"),不要传exc_info参数(它会自动捕获当前sys.exc_info()) - 确保 logger 已配置了文件 handler,否则日志仍只输出到控制台
- 如果需要额外上下文(如用户 ID、请求 ID),用
extra参数传字典,别拼在消息字符串里
配置 RotatingFileHandler 避免日志文件无限膨胀
直接用 FileHandler 写日志,出错一多,单个文件就涨到几百 MB,排查困难,还可能拖慢应用。Python 标准库的 RotatingFileHandler 能自动轮转、压缩、限制数量。
关键参数差异:
-
maxBytes=10*1024*1024(即 10MB)比默认 0 更实用,避免单文件失控 -
backupCount=5表示最多保留 5 个历史文件(.log.1~.log.5),再老的会被删掉 - 若需压缩旧日志,得自己继承并重写
doRollover(),标准版不支持 gzip;可改用TimedRotatingFileHandler配合外部脚本归档
注意:Windows 下多个进程同时写同一日志文件会出错,此时应改用 WatchedFileHandler 或上 ConcurrentLogHandler(需 pip install)。
自定义 Formatter 确保堆栈可读且含关键字段
默认的 Formatter 输出的堆栈缩进混乱、缺少时间戳或模块名,查问题时要反复滚动。必须显式设置 format 字符串,并启用 datefmt。
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
推荐格式字符串:
%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s
这样每行开头有精确到毫秒的时间,级别对齐,函数名和行号明确指向出错位置;而堆栈本身会自动追加在 message 后,保持原缩进结构。
易踩的坑:
- 漏写
%(asctime)s或没设datefmt,导致时间格式为默认 ISO8601(含微秒,太长) - 在多线程/协程环境里,仅靠
%(threadName)s不够区分请求,需结合 request ID 注入extra - formatter 设了但没绑定到 handler,等于白配
在顶层异常钩子中兜底记录未捕获异常
不是所有异常都会进你的 try...except——比如主线程外的线程抛异常、asyncio 任务崩溃、信号处理回调出错,这些会触发 sys.excepthook 或 threading.excepthook,默认只打屏,不落盘。
必须主动接管:
- 重置
sys.excepthook = lambda *args: logging.getLogger().critical("Uncaught exception", exc_info=args) - 对线程设
threading.excepthook = ...(Python 3.8+),旧版本需在Thread.run()外包一层try/except - asyncio 要监听
loop.set_exception_handler(),否则create_task()抛的异常静默消失
这里最容易被忽略的是:兜底 hook 里不能依赖尚未初始化的 logger(比如还没调 basicConfig),否则会 fallback 到 stderr,看似写了日志,实则没进文件。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










