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

Python程序退出时如何控制未捕获异常的日志记录方式?

Python日志JSON格式化与异常退出的正确处理

我编写了如下Python程序,希望所有日志以JSON格式输出(方便CloudWatch检索),异常回溯信息放在"exception"字段中;同时程序发生异常时要以错误状态退出,且回溯信息不会拆分为多事件:

import logging

if __name__ == "__main__":
    # 配置日志记录器以JSON格式写入日志,并将异常回溯信息放在"exception"字段中

    logger = logging.getLogger(__name__)

    logger.info("worker started")

    try:
        # 执行一些工作任务

        logger.info("worker finished successfully")

    except Exception:
        logger.exception("unknown error occurred")
        # 此处该如何处理?

我尝试了三种方案,但都有问题:

  • 方案一:仅记录日志,程序正常退出(不符合错误退出要求)
    except Exception:
        logger.exception("unknown error occurred")
    
  • 方案二:记录日志后raise,程序错误退出,但会额外输出未格式化的异常(ECS中显示为多行,不符合JSON格式要求)
    except Exception:
        logger.exception("unknown error occurred")
        raise
    
  • 方案三:记录日志后用sys.exit(1),程序错误退出,但调试时无法直接定位到异常发生位置(跳转到sys.exit行而非错误源),且sys.exit的使用不被推荐
    except Exception:
        logger.exception("unknown error occurred")
        sys.exit(1)
    

有没有一种正确的方式,既能让程序因真实异常直接退出,又仅通过已配置的日志记录器按预期记录回溯信息?


正确解决方案:用全局异常钩子处理未捕获异常

核心思路是别在顶层捕获所有Exception,而是通过sys.excepthook接管未捕获异常的处理逻辑——这样既保证异常用你配置的JSON格式记录,程序也会因真实异常退出,调试时还能直接定位到错误发生的位置。

具体步骤:

  1. 配置自定义JSON日志格式化器,确保能把异常回溯信息塞进"exception"字段
  2. 设置sys.excepthook,让所有未捕获的异常都通过日志记录器输出,代替Python默认的stderr打印
  3. 删掉顶层的try-except块,让异常自然冒泡到全局钩子处理

示例代码:

import logging
import sys
from pythonjsonlogger import jsonlogger  # 也可以自己写自定义Formatter,用第三方库更省心

def setup_logging():
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.INFO)
    
    # 配置JSON格式,包含异常字段
    formatter = jsonlogger.JsonFormatter(
        "%(asctime)s %(levelname)s %(message)s %(exception)s"
    )
    
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
    # 关掉日志传播,避免默认处理器重复输出
    logger.propagate = False
    return logger

def handle_uncaught_exception(exc_type, exc_value, exc_traceback):
    # 键盘中断(Ctrl+C)按默认逻辑处理
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    
    logger = logging.getLogger(__name__)
    # 用CRITICAL级别记录异常,传入exc_info拿到完整回溯
    logger.critical("uncaught exception occurred", exc_info=(exc_type, exc_value, exc_traceback))

# 把全局异常钩子换成我们自己的
sys.excepthook = handle_uncaught_exception

if __name__ == "__main__":
    logger = setup_logging()
    logger.info("worker started")
    
    # 执行工作任务,不用包try-except
    # 模拟一个异常
    1 / 0
    
    logger.info("worker finished successfully")

这个方案的优势:

  • 所有日志都是统一的JSON格式,异常回溯老老实实待在"exception"字段里,CloudWatch里不会拆成多条事件
  • 程序因真实异常退出,调试的时候直接跳转到出错的那一行,保留原生调试体验
  • 不会有重复日志,所有输出都走你配置的日志记录器
  • 避开了sys.exit带来的调试定位问题

若必须捕获特定异常:

要是你得抓部分异常做额外操作,记录日志后直接raise就行——因为全局钩子已经接管了异常输出,不会再冒出未格式化的内容:

try:
    # 执行某个特定操作
    some_operation()
except SpecificError as e:
    logger.error(f"特定错误发生: {e}", exc_info=True)
    raise  # 重新抛出,让全局钩子处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 08:00:33