TATECHATLAS
◎ 简体中文
编程

Python 异常日志记录:保留追踪信息和足够的请求上下文

使用命名的日志记录器,在应用程序层面配置一次,并附加安全的上下文,而不用在每个错误上都重复配置。

本文内容

在异常处理程序内部,logger.exception 会记录一个带有当前异常信息的 ERROR 事件。应在应用程序入口点配置处理程序,并在各个模块中使用 getLogger(__name__)。包含一个安全的操作或请求标识符,以便能够将追踪信息与失败的操作关联起来。日志记录了失败事件;程序必须单独决定是进行恢复、返回错误还是重新引发异常。

选择你需要记录的事件

一个异常对象和一个操作事件回答的是不同的问题。异常描述了什么失败了;事件可以标识当时正在进行哪个操作。在添加日志记录之前,先决定谁会阅读这条记录,以及他们需要什么信息来进行调查。一个简短的消息,用于命名操作并提供一个非秘密的关联标识符,通常比重复整个请求体或 dumping 所有局部变量更有用。

在应用程序边界配置日志记录

对于独立脚本,应在应用程序开始工作之前配置日志记录。下面的示例使用 basicConfig 一次,并获取一个命名的日志记录器。更大的应用程序可能会使用不同的配置机制,但所有权应该仍然清晰。一个可重用的库应暴露命名的日志记录器,并让其调用者选择处理程序、目标和级别,而不是无条件地替换应用程序配置。

在处理程序内部使用异常信息

logger.exception 是为异常处理程序设计的,在那里当前的异常信息是可用的。仅使用异常文本调用 logger.error 会省略追踪信息,除非显式请求异常信息。反过来,写入追踪信息并不意味着要将每个可恢复的条件都视为致命的应用程序失败。根据操作来选择事件级别和恢复策略,而不仅仅根据异常类的名称。

阅读一个完整的小型示例

这个构造的独立脚本故意将一个非数字字符串传递给 int。预期的日志包含一个带有 request_id=demo1 的 ERROR 消息,以及以 ValueError 结尾的异常信息。确切的追踪路径和行号取决于脚本保存的位置,因此不承诺固定的完整追踪信息。该示例演示了一个日志调用;其捕获的异常不会自动传播给调用者。

import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s %(message)s")
logger = logging.getLogger(__name__)
request_id = "demo1"
try:
    int("bad")
except ValueError:
    logger.exception("Could not parse quantity; request_id=%s", request_id)

决定谁拥有最终的错误记录

如果较低层记录了一个异常并重新引发它,而较高层也记录了它,那么一个失败可能会出现两次。决定哪个层有足够的上下文来创建操作记录。较低层可以通过引发适当的异常来添加信息,而边界层记录最终的失败。这是一个设计选择,而不是要求每个异常都必须在每个地方恰好记录一次。

通过处理程序诊断重复输出

看起来重复的记录也可能源于将处理程序附加到子日志记录器,同时允许传播到具有另一个处理程序的祖先。请在移除应用程序事件之前检查日志记录器层次结构和处理程序配置。日志记录器级别和处理程序级别都可能影响输出是否出现。在不了解目标的情况下抑制传播,可能会隐藏来自中央接收端的记录,同时也会移除重复的控制台行。

将敏感上下文排除在记录之外

使用一个不透明的请求标识符或精心选择的操作名称。不要包含密码、授权头、API 密钥或整个未经过滤的请求负载。异常消息本身可能包含用户输入或连接细节,因此一个安全的日志调用并不能证明生成的每个追踪信息都适合保留。应根据实际应用程序和日志目标,应用访问、保留和脱敏策略。

将诊断与程序行为分开

日志调用既不会重试操作,也不会选择返回给客户端的响应。在记录事件之后,应有意决定是使用有效的回退继续、返回文档化的失败,还是重新引发。在错误边界附近记录这一选择。如果程序继续,请避免让后续代码依赖于由失败语句从未成功创建的值。

检查清单

  • 在应用程序层面配置处理程序,而不是在每个模块中都配置。
  • 在处理异常的同时使用异常信息。
  • 包含一个安全的关联标识符。
  • 当输出重复时,检查处理程序和传播设置。
  • 将恢复或传播的决策与日志记录分开。

该脚本演示了标准库日志记录和一个捕获的 ValueError。它不是一个完整的生产日志配置,也不展示已部署的日志接收端、自动脱敏、重试机制或执行过的应用程序测试。

参考来源

  1. Python: logging reference ↗
  2. Python: logging how-to ↗
返回顶部 ↑