Python 异常日志如何避免信息丢失

2次阅读

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

Python 异常日志如何避免信息丢失

捕获异常时必须用 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()
  • 不要在 finallyelse 块中误用 exception() —— 它只应在 except 中调用

线程/协程环境下需确保异常上下文不被覆盖

threadingasyncio 中,未捕获异常可能静默消失,或因线程切换导致 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 —— 这部分最容易被忽略。

text=ZqhQzanResources