You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:21:13