必须用 traceback.format_exc() 或 logger.exception() 获取完整异常信息,否则丢失堆栈、文件名、行号等关键定位信息;logger.exception() 仅限 except 块中使用,且需避免传入不可靠变量或在自定义 Formatter 中忽略 record.exc_text。

捕获异常时必须用 traceback.format_exc() 而非 str(e)
直接打印 str(e) 或 repr(e) 只能拿到异常消息,丢失堆栈、文件名、行号、上下文变量等关键信息。日志里只看到 "KeyError: 'user_id'",根本无法定位是哪次请求、哪个函数、哪行代码出的问题。
正确做法是显式调用 traceback.format_exc() 获取完整 traceback 字符串:
import traceback
try:
risky_operation()
except Exception as e:
logger.error("操作失败:%s", traceback.format_exc())注意:logger.exception() 内部已自动调用该函数,但仅限在 except 块中使用;若需自定义日志格式或异步记录,必须手动调用。
使用 logger.exception() 时不能传入额外参数
logger.exception(msg, *args) 会自动附加 traceback,但前提是 *args 不干扰格式化逻辑。常见错误是写成:
立即学习“Python免费学习笔记(深入)”;
logger.exception("处理 %s 失败", user_id) # ❌ 可能导致格式化异常或 traceback 被截断更安全的方式是把上下文信息放在消息里,不依赖 *args:
logger.exception("处理用户 %s 失败", user_id) # ✅ 正确(前提是 user_id 是字符串或能安全 str())
# 或更稳妥:
logger.exception("处理用户 %r 失败,原始错误:%s", user_id, e)- 避免在
exception()中传入可能为None或含特殊字符的变量 - 如果必须动态拼接,优先用 f-string 构造完整消息,再传给
exception() - 不要在
finally或else块中误用exception()—— 它只应在except中调用
多线程/协程环境下需确保异常上下文不被覆盖
在 threading 或 asyncio 中,未捕获异常可能静默消失,或因线程切换导致 sys.exc_info() 被覆盖。例如:
def worker():
try:
raise ValueError("boom")
except Exception:
# 若这里延迟记录(如发到队列),其他线程可能已触发新异常
log_queue.put(traceback.format_exc())解决方案:
- 立即调用
traceback.format_exc(),不要延迟到回调或队列中 - 协程中避免在
except外使用sys.exc_info()—— 它不跨 await 边界可靠 - 对关键任务,用
loop.set_exception_handler()(asyncio)或threading.excepthook捕获未处理异常
日志处理器本身不能丢弃 traceback 字段
有些自定义 Handler(比如发 HTTP 的)会把日志当作 dict 处理,若未显式提取 record.exc_text,就会丢失 traceback。默认 Formatter 用 %(exc_text)s 渲染,但如果你重写了 format() 方法:
class MyFormatter(logging.Formatter):
def format(self, record):
# ❌ 忘记调用 super().format(record) 或处理 record.exc_text
return f"{record.levelname}: {record.message}"务必检查:record.exc_text 是否为空;若非空,应将其拼入最终字符串,否则 traceback 彻底消失。
复杂点往往不在捕获逻辑,而在于日志从 record 到落盘/上传的每个中间环节是否透传了 exc_text —— 这部分最容易被忽略。










