Python 排查脚本错误:用 logging 保留异常堆栈
批处理脚本报错时,一句“处理失败”通常不够。我们还需要知道失败发生在哪个函数、哪一行,以及异常经过了怎样的调用路径。Python 标准库 logging 可以把这些信息放进日志,无需额外依赖。下面用一个可运行的小例子说明配置位置、异常记录和重复日志三个常见问题。
只记录异常文字,会丢掉定位线索
logger.error("失败:%s", exc) 会写出异常的文字,但默认不附带堆栈。两处代码都可能抛出相同的 ValueError,仅靠文字无法区分调用路径。在 except 块中改用 logger.exception(...),就能以 ERROR 级别记录当前异常及 traceback。
import logging
logger = logging.getLogger(__name__)
def parse_count(text):
return int(text)
def main():
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(name)s %(message)s",
)
logger.info("开始处理批次 batch-001")
try:
count = parse_count("abc")
except ValueError:
logger.exception("批次 batch-001 的数量格式无效")
return 1
logger.info("处理完成,数量=%d", count)
return 0
if __name__ == "__main__":
raise SystemExit(main())
保存后运行,日志会包含开始信息、错误说明、以 Traceback 开头的堆栈,以及最终的 ValueError。示例捕获的是数量格式错误,返回状态码 1;它没有继续执行成功分支。若输入改为 "12",则记录成功信息并返回 0。logging 默认输出到标准错误流,终端中看到日志不代表程序使用了 print。
exception 应放在处理异常的位置
logger.exception("处理失败") 可理解为 ERROR 日志加上 exc_info=True。它读取当前正在处理的异常,因此应放在 except 块内。离开异常处理上下文后再调用,通常无法得到想要的堆栈,甚至只留下 NoneType: None。
也可以在 except 内使用 logger.error("处理失败", exc_info=True),两者在这里都能保留异常信息。是否重新抛出异常则是另一项决定:命令行入口可以返回非零状态码;库函数常常应让上层决定如何处理。避免每一层都记录再抛出,否则同一次失败会出现多份相似堆栈。
配置一次,模块只取得自己的 logger
把 basicConfig 放在应用入口,让业务模块使用 getLogger(__name__)。模块名会进入日志,便于区分来源。不要在每次处理请求或每次调用函数时创建并添加 StreamHandler;反复添加处理器会让一条消息输出多次。
默认情况下,模块 logger 的记录会向祖先传播。如果同时给模块 logger 和根 logger 添加处理器,也可能重复输出。先检查处理器配置与 propagate,再决定是否调整,不要看到重复就直接关闭所有传播。
日志仍需控制内容与级别
示例把默认级别设为 INFO,因此 DEBUG 信息不会显示。排障时可以临时调整级别。使用 logger.info("数量=%d", count) 这种参数形式,格式化可延迟到确实需要输出时;但传入参数前执行的函数调用依然会发生,昂贵计算仍要单独判断是否必要。
记录脱敏的批次号和操作阶段,通常比打印整份输入更有帮助。异常文字本身也可能带上输入值,因此密码、令牌、完整个人资料不能直接进入异常或日志。日志是否足够好,关键在于能否据此定位失败,同时让成功、失败和退出状态保持一致。
- 点赞
- 收藏
- 关注作者
评论(0)