Python自定义异常类自动记录日志的优化方案咨询
解决自定义异常自动记录日志的问题
我完全懂你的痛点——每次在异常处理块里既要抛出自定义异常,又要手动写重复的日志代码,既繁琐又容易出错。而且当前代码里logger.exception显示NoneType: None,核心原因是在自定义异常的__init__中调用它时,没有正确捕获到有效的底层异常上下文(sys.exc_info()返回了空值)。
下面给你几个精简代码的方案,让自定义异常自动带上正确的异常信息日志:
方案1:通用日志异常基类(最推荐)
创建一个LoggedException基类,所有自定义异常都继承它。这个基类会自动判断是否处于异常上下文,并智能记录日志:
import logging import sys # 初始化日志配置(根据你的实际需求调整格式和级别) logger = logging.getLogger('exceptions_module') logging.basicConfig( level=logging.ERROR, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) class LoggedException(Exception): def __init__(self, error_msg): super().__init__(error_msg) # 获取当前的异常上下文 exc_info = sys.exc_info() # 只有在异常处理块中创建异常时,才记录带栈信息的日志 if exc_info[0] is not None: logger.exception(error_msg, exc_info=exc_info) else: # 如果是主动抛出的异常(不在except块中),只记录普通错误日志 logger.error(error_msg) # 你的自定义异常只需继承这个基类 class CustomError(LoggedException): pass def forced_exception(): try: raise Exception('some_exception_occured') except Exception as error_msg: # 执行清理或其他业务操作 # ... # 直接抛出自定义异常,无需额外写日志代码 raise CustomError(f"处理失败:{str(error_msg)}")
为什么这个方案有效?
- 在
except块中创建CustomError时,sys.exc_info()会返回当前捕获的底层异常信息,我们明确把它传给logger.exception,就能正确记录完整的异常栈。 - 所有自定义异常只需继承基类,无需重复编写日志逻辑,彻底消除代码冗余。
方案2:结合raise ... from关联异常链
Python支持用raise NewException(...) from OriginalException关联异常链,这样日志会自动显示原异常的信息,同时进一步简化日志逻辑:
import logging import sys logger = logging.getLogger('exceptions_module') logging.basicConfig(level=logging.ERROR) class CustomError(Exception): def __init__(self, error_msg): super().__init__(error_msg) # 检查是否有通过raise...from关联的原异常 if self.__cause__ is not None: logger.exception(error_msg, exc_info=(type(self.__cause__), self.__cause__, self.__cause__.__traceback__)) else: exc_info = sys.exc_info() if exc_info[0] is not None: logger.exception(error_msg, exc_info=exc_info) else: logger.error(error_msg) def forced_exception(): try: raise Exception('some_exception_occured') except Exception as error_msg: # 执行清理操作 # ... # 用from关联原异常,保留完整异常链 raise CustomError(f"用户友好的错误提示:{str(error_msg)}") from error_msg
优势:
- 日志中会显示完整的异常链(原异常→自定义异常),极大方便问题排查。
- 不需要手动传递
exc_info(),代码更简洁优雅。
方案3:装饰器统一处理函数异常
如果你的异常主要出现在函数中,用装饰器可以彻底消除函数内的重复异常处理代码:
import logging import sys from functools import wraps logger = logging.getLogger('exceptions_module') logging.basicConfig(level=logging.ERROR) class CustomError(Exception): pass def log_and_raise_custom(error_msg_template): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: user_friendly_msg = error_msg_template.format(error=str(e)) logger.exception(user_friendly_msg) raise CustomError(user_friendly_msg) from e return wrapper return decorator # 给需要处理的函数加上装饰器 @log_and_raise_custom("执行[{func_name}]操作失败:{error}") def forced_exception(): raise Exception('some_exception_occured')
适合场景:
- 多个函数需要抛出相同类型的自定义异常,且错误提示模板类似。
- 完全把异常处理和日志逻辑从业务函数中剥离,代码更整洁。
原代码问题复盘
当你在CustomError的__init__中直接调用logger.exception()时,它默认会调用sys.exc_info()获取当前异常上下文,但如果调用时机不对(比如异常上下文已失效),或者没有明确传入有效的异常信息,就会出现NoneType: None的情况。上面的方案通过主动捕获并传入有效的exc_info,彻底解决了这个问题。
内容的提问来源于stack exchange,提问作者Jags
相关产品推荐
相关产品推荐

