Python自定义包如何仅自动记录自身抛出的所有异常日志
Python自定义包仅记录内部异常的日志实现方案
我在开发自定义Python包时需要内置日志功能,包内逻辑在输入不合法、校验失败等场景下都会抛出异常,要求通过Python标准库的logging模块,完整记录包内部抛出的所有异常。
现有方案的缺陷
目前调研到的两类通用实现都存在明显问题,无法直接使用:
- 逐段异常捕获:最朴素的实现是给所有可能抛出异常的代码块包裹try-except逻辑,示例如下:
import logging try: raise Exception except Exception as e: logging.error(e) raise
这种方案仅适合异常抛出点极少的微型工具包,对于规模稍大的包,给每一处抛异常的代码都加捕获逻辑的维护成本极高,可维护性极差。
- 全局替换
sys.excepthook:通过自定义函数重写系统异常钩子的方案本身日志记录能力符合预期,但直接替换全局钩子会把终端用户自身代码抛出的异常也全部纳入包的日志记录范围,属于越权行为,不符合预期,问题复现场景如下:
# 终端用户侧代码 import my_custom_package raise Exception # 用户自己抛出的异常会被包的日志逻辑错误记录
实现要求
合格的方案需要同时满足三个约束:
- 范围严格可控:仅记录包内部产生的异常,绝不记录用户侧代码抛出的异常
- 低侵入性:不需要逐行修改包内每一处抛出异常的代码
- 无副作用:不修改全局异常处理的默认行为,不对终端用户的正常代码逻辑造成干扰
落地实现代码
直接在包的根目录__init__.py中添加如下代码即可,注意补全依赖导入:
# 包根目录 __init__.py import sys import logging import traceback from pathlib import Path # 初始化包专属日志实例 logger = logging.getLogger(__name__) def _package_exception_handler(exc_type, exc_value, exc_traceback): # 键盘中断类异常走系统默认处理,不做拦截 if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return # 提取异常最内层抛出点的文件路径 exc_origin_file = Path(traceback.extract_tb(exc_traceback)[-1].filename) package_root = Path(__file__).parent # 如果异常抛出点不在当前包的目录范围内,走系统默认处理逻辑 if package_root not in exc_origin_file.parents: sys.__excepthook__(exc_type, exc_value, exc_traceback) return # 确认是包内部抛出的未捕获异常,记录完整错误栈 logger.error( "Uncaught exception from package", exc_info=(exc_type, exc_value, exc_traceback) ) # 挂载自定义异常钩子 sys.excepthook = _package_exception_handler
内容的提问来源于stack exchange,提问作者Kyle Carow
相关产品推荐
相关产品推荐

