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

Python自定义异常Trace列表累积旧异常问题求助

问题描述

我正在开发一个Python程序,需通过ClickHouse自定义日志记录所有异常及其上下文以辅助调试。为此实现了CustomException类存储完整异常路径(含API调用、CRUD操作及参数等),并通过auto_logger装饰器自动化该异常日志流程。但应用装饰器后发现,CustomException的traces列表会保留之前的异常,导致日志中旧异常不断累积。尝试过删除异常实例、重新初始化日志,但仅重启服务器能暂时解决,再次调用后问题复现。现求助排查该累积问题的原因及解决方法,期望找到内存清理或装饰器重初始化的方案。

相关代码实现如下:

class ExceptionSnippet:
    def __init__(self, file_name, func_name, msg, error_level):
        self.file_name = file_name
        self.func_name = func_name
        self.msg = msg
        self.error_level = error_level

class CustomException(BaseException):
    error_code: int
    value: Any
    traces: list[ExceptionSnippet] = []

    def __init__(self, arg=None):
        if isinstance(arg, CustomException):
            self.error_code = arg.error_code
            self.value = arg.value
            self.traces = arg.traces
def auto_logger(file_name, func_name, error_code):
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except CustomException as ex:
                ex.traces.append(
                    ExceptionSnippet(
                        file_name, func_name, ex.traces[-1].func_name, 'trace'
                    ))
                raise ex
            except Exception as ex:
                error = CustomException()
                error.error_code = error_code
                error.traces.append(ExceptionSnippet(
                    file_name, func_name, ex.args[0], 'error'
                ))
                raise error 
        return wrapper
    return decorator
问题原因

核心问题出在CustomException类的traces属性定义上——它被声明为类属性,而非实例属性。在Python中,类属性是所有实例共享的,所有CustomException实例都会指向同一个列表对象:

  • 第一次创建实例并向traces添加内容时,这个列表会被修改,后续所有新创建的实例(包括通过CustomException(arg)复制的实例)都会复用这个已修改的类属性列表,导致旧异常痕迹不断累积。
  • 即使删除单个异常实例,类属性本身依然存在,所以重启服务器前,这个共享列表的状态不会被重置。
解决方法

1. 将traces改为实例属性

修改CustomException的定义,把traces移到__init__方法中,确保每个实例都拥有独立的列表:

class CustomException(BaseException):
    error_code: int
    value: Any

    def __init__(self, arg=None):
        # 每个实例初始化独立的traces列表
        self.traces: list[ExceptionSnippet] = []
        if isinstance(arg, CustomException):
            self.error_code = arg.error_code
            self.value = arg.value
            # 复制传入实例的traces,而非直接引用
            self.traces = arg.traces.copy()

使用arg.traces.copy()是为了避免复制实例时共享同一个列表,确保每个实例的traces都是独立副本。

2. 验证装饰器逻辑

修改后,装饰器中的异常处理逻辑无需额外调整,因为每个CustomException实例的traces都是独立的:

def auto_logger(file_name, func_name, error_code):
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except CustomException as ex:
                ex.traces.append(
                    ExceptionSnippet(
                        file_name, func_name, ex.traces[-1].func_name, 'trace'
                    ))
                raise ex
            except Exception as ex:
                error = CustomException()
                error.error_code = error_code
                error.traces.append(ExceptionSnippet(
                    file_name, func_name, ex.args[0], 'error'
                ))
                raise error 
        return wrapper
    return decorator

3. 效果确认

修改后:

  • 新创建的CustomException实例不会携带之前异常的痕迹
  • 复制实例时会创建traces的副本,不会影响原实例的列表
  • 无需重启服务器,每次请求的异常日志都会独立存储

内容的提问来源于stack exchange,提问作者Miky Mike

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 01:45:21