Python 日志与异常处理:构建可观测的生产程序
日志的价值在于重建一次失败发生时的上下文,而不是记录越多越好。异常负责沿调用链传递失败,日志负责在合适的边界留下证据;两者分工清晰,才能避免重复堆栈和难以检索的文本。
核心原则
- 为模块创建命名 logger,在应用入口统一配置级别、输出格式和处理器。
- 捕获异常时只处理能够恢复的情况,否则使用 raise 保留原始堆栈。
- 用 raise NewError(...) from exc 建立异常链,补充业务操作语境。
- 日志字段包含 request_id、operation、duration 与结果,避免拼接不可检索的长文本。
- 对预期业务错误记录 info 或 warning,对真正不可恢复错误记录 exception。
推荐的实践步骤
在 Web 或任务消费入口生成关联标识,并通过上下文传递到服务层。底层只包装异常,不重复记录;边界捕获已知领域异常并返回明确结果,未知异常使用 logger.exception 记录一次完整堆栈。告警依据错误率和延迟聚合触发,不应让每一条日志都成为通知。
logger = logging.getLogger(__name__)
try:
result = service.run(task_id)
except DomainError as exc:
logger.warning("task_rejected", extra={"task_id": task_id})
raise
except Exception:
logger.exception("task_failed", extra={"task_id": task_id})
raise
示例用于说明实现思路,实际项目还应结合所使用的框架版本、部署环境和业务约束进行调整。重要配置要进入版本管理,并在测试环境验证后再发布。
常见误区
- 使用 except Exception: pass 会吞掉失败,让数据在不完整状态下继续流转。
- 日志中输出密码、令牌或完整请求体,会造成严重的敏感信息风险。
- 每层都打印同一异常堆栈,会让真正的根因淹没在重复内容中。
上线前检查清单
- 确认“为模块创建命名 logger,在应用入口统一配置级别、输出格式和处理器”已经通过代码审查或运行验证。
- 确认“捕获异常时只处理能够恢复的情况,否则使用 raise 保留原始堆栈”已经通过代码审查或运行验证。
- 确认“用 raise NewError(...) from exc 建立异常链,补充业务操作语境”已经通过代码审查或运行验证。
- 确认“日志字段包含 request_id、operation、duration 与结果,避免拼接不可检索的长文本”已经通过代码审查或运行验证。
- 为失败路径、边界条件和回滚方案准备测试或演练记录。
- 上线后观察错误率、延迟和资源消耗,确认变化符合预期。
总结
Python 日志与异常处理:构建可观测的生产程序的关键在于把隐含假设变成可执行的约束,并通过测试、监控和复盘持续验证。先从影响最大的真实场景开始,小步调整并保留回滚能力,通常比一次性大范围改造更安全,也更容易积累可复用的工程经验。