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

非Debug模式下生产环境异常捕获的优化方案咨询

问题描述

我编写了一个可通过-d, --debug参数执行的程序。在生产环境运行时,我不希望用户看到traceback信息。目前我采用分支处理:非Debug模式下使用全局try..catch语句,代码如下:

@click.command()
@click.option('-d', '--debug', help='Debug mode', is_flag=True)
def cli(debug, **kwargs):
    runner = Runner(**kwargs)
    if not debug:
        try:
           failures = runner.run()
        except Foo as e:
           handleFooException(e)
           exit(1)
        except Bar as e: 
           handleBarException(e)
           exit(1)
        except e:
           if hasattr(e, 'msg'):
              click.secho(f'Error: {e.msg})'
              exit(1)
    else: 
        failures = runner.run()
    click.echo('')
    return failures


if __name__ == '__main__':
    cli()

该方案存在以下弊端:

  • __main__.py需要知晓程序中所有异常类型的处理方式
  • 实现繁琐

请问在生产环境中,是否有更优的异常处理替代方案?


优化方案

方案1:自定义异常基类+集中处理

先定义项目通用的异常基类,所有业务异常都继承这个基类,全局只捕获基类和通用异常,避免逐个列举异常类型:

class AppBaseException(Exception):
    def __init__(self, msg, exit_code=1):
        self.msg = msg
        self.exit_code = exit_code

# 业务异常继承基类
class Foo(AppBaseException):
    pass

class Bar(AppBaseException):
    pass

@click.command()
@click.option('-d', '--debug', help='Debug mode', is_flag=True)
def cli(debug, **kwargs):
    runner = Runner(**kwargs)
    try:
        failures = runner.run()
    except AppBaseException as e:
        click.secho(f'Error: {e.msg}', fg='red')
        exit(e.exit_code)
    except Exception as e:
        if debug:
            # 调试模式抛出原始异常,显示完整traceback
            raise
        # 生产环境只显示通用错误信息
        click.secho(f'Unexpected error: {str(e)}', fg='red')
        exit(1)
    click.echo('')
    return failures

这种方式下__main__.py无需关心所有具体业务异常类型,代码结构更简洁。

方案2:利用全局异常钩子

通过替换sys.excepthook统一处理异常,结合debug参数控制traceback的显示:

import sys
import traceback

@click.command()
@click.option('-d', '--debug', help='Debug mode', is_flag=True)
def cli(debug, **kwargs):
    if not debug:
        def custom_excepthook(exc_type, exc_value, exc_traceback):
            # 处理自定义业务异常
            if issubclass(exc_type, AppBaseException):
                click.secho(f'Error: {exc_value.msg}', fg='red')
                sys.exit(exc_value.exit_code)
            # 处理其他未预期异常
            else:
                click.secho(f'Unexpected error: {str(exc_value)}', fg='red')
                sys.exit(1)
        sys.excepthook = custom_excepthook
    
    runner = Runner(**kwargs)
    failures = runner.run()
    click.echo('')
    return failures

这种方式不需要在代码中嵌套大段try-except,把异常处理逻辑抽离到全局钩子中,主逻辑更清爽。调试模式下不替换钩子,自然会显示完整traceback。

方案3:封装Runner的异常处理

把异常处理逻辑整合到Runner类内部,根据传入的debug参数决定是否抛出异常:

class Runner:
    def __init__(self, debug=False, **kwargs):
        self.debug = debug
        # 其他初始化逻辑
    
    def run(self):
        try:
            # 业务逻辑代码
            pass
        except Foo as e:
            if self.debug:
                raise
            handleFooException(e)
            exit(1)
        except Bar as e:
            if self.debug:
                raise
            handleBarException(e)
            exit(1)
        except Exception as e:
            if self.debug:
                raise
            if hasattr(e, 'msg'):
                click.secho(f'Error: {e.msg}', fg='red')
            else:
                click.secho(f'Unexpected error: {str(e)}', fg='red')
            exit(1)

@click.command()
@click.option('-d', '--debug', help='Debug mode', is_flag=True)
def cli(debug, **kwargs):
    runner = Runner(debug=debug, **kwargs)
    failures = runner.run()
    click.echo('')
    return failures

这样__main__.py完全不需要关心异常细节,只需要传递debug参数即可,异常处理和业务逻辑的耦合更合理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 10:45:37