非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
相关产品推荐
相关产品推荐

