logging.exception 只在 except 块内有效,因它依赖 sys.exc_info() 提供的异常上下文;离开 except 块后该上下文清空,日志将无堆栈。

logging.exception 不是“自动”记录错误堆栈的魔法开关,它只在当前异常上下文(sys.exc_info() 非空)中才有效——换言之,必须在 except 块里调用,否则日志里只会写“None”,没有堆栈。
为什么 logging.exception 在 try/except 外调用没堆栈
logging.exception 本质是 logger.error(msg, exc_info=True) 的快捷写法,而 exc_info=True 表示“取当前线程最近一次未处理的异常信息”。Python 只在 except 子句执行时把异常暂存进 sys.exc_info();一旦离开该 except 块,这个上下文就被清空。
- 错误写法:
try: 1 / 0 except ZeroDivisionError: pass logging.exception("出错了") # ❌ 这里 exc_info 是空的,日志无堆栈 - 正确写法:
try: 1 / 0 except ZeroDivisionError: logging.exception("除零错误") # ✅ 在 except 内,堆栈完整输出
logging.exception 和 logging.error(..., exc_info=True) 的等价性
两者行为完全一致,只是语法糖。但理解这点能避免误用:比如你想在捕获后做些判断再决定是否记录,就该显式传 exc_info,而不是硬套 exception。
-
logging.exception("msg")≡logging.error("msg", exc_info=True) - 若想记录特定异常(非当前上下文),得手动传元组:
logging.error("msg", exc_info=(etype, evalue, tb)) - 若只想记录当前异常但不用默认 ERROR 级别,可用:
logging.warning("msg", exc_info=True)
常见陷阱:多层 except 或 finally 中调用 exception
嵌套 except 或在 finally 里调用 logging.exception 很容易拿错堆栈——Python 的异常上下文是动态覆盖的,外层 except 会覆盖内层留下的 sys.exc_info()。
- 问题代码:
try: try: raise ValueError("内层错误") except ValueError: logging.exception("内层捕获") # ✅ 正确记录 raise # ⚠️ 重新抛出,会覆盖 sys.exc_info() except ValueError: logging.exception("外层捕获") # ✅ 记录的是重新抛出后的异常,但原始 traceback 已被截断 - 安全做法:在 re-raise 前先保存,或直接用
exc_info=sys.exc_info()显式传递:except ValueError: exc_info = sys.exc_info() logging.error("外层处理", exc_info=exc_info)
真正要注意的不是“怎么调用”,而是“调用时异常上下文是否还活着”。很多线上日志看不到堆栈,不是配置问题,是日志语句跑出了 except 块,或者中间有隐式异常覆盖(比如 raise、return、sys.exc_clear())。检查 sys.exc_info() 是否为 (None, None, None),比背 API 更管用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











