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

如何在异常钩子中利用exc_info替换日志记录的来源信息?

如何让日志记录使用exc_info中的异常栈位置而非日志调用位置

当日志调用时传入exc_info参数,能否用它来填充日志记录的文件、行号等位置信息,替代默认的日志调用位置?

我用sys.excepthook捕获未捕获的异常,常规日志的位置信息(文件、行号、函数名)是基于日志被调用的位置生成的。这导致异常钩子中生成的日志记录,位置信息指向钩子函数本身,而非异常实际发生的位置。

代码示例

文件1: log_record_test.py

# File log_record_test.py
import logging
import log_record_setup
import sys


sys.excepthook = log_record_setup.exc_hook
logger = logging.getLogger()
logger.addHandler(log_record_setup.LogHandler())


def test():
    logger.critical("Normal logging here")


def test2():
    raise TypeError()


test()
test2()

文件2: log_record_setup.py

# File log_record_setup.py
import logging

logger = logging.getLogger("TestLogger")


class LogHandler(logging.Handler):
    def __init__(self):
        super(LogHandler, self).__init__()

    def emit(self, record):
        self.format(record)
        log_record_dict = record.__dict__.copy()
        print(f"{log_record_dict['filename']} at {log_record_dict['lineno']} within {log_record_dict['funcName']}")


def exc_hook(exc_type, exc_value, tb):
    logger.critical("Error happened", exc_info=(exc_type, exc_value, tb))

当前运行输出

运行log_record_test.py后,输出如下:

log_record_test.py at 12 within test
log_record_setup.py at 18 within exc_hook

第一个日志输出符合预期,但第二个对应test2抛出的异常,日志位置指向了钩子函数exc_hook,而非TypeError实际抛出的test2函数位置。


解决方案

方案1:在异常钩子中手动替换位置信息

直接在exc_hook里解析traceback,提取异常发生的真实位置,通过extra参数覆盖日志记录的默认位置字段:

修改后的log_record_setup.py代码:

# File log_record_setup.py
import logging
import traceback
import os

logger = logging.getLogger("TestLogger")


class LogHandler(logging.Handler):
    def __init__(self):
        super(LogHandler, self).__init__()

    def emit(self, record):
        self.format(record)
        log_record_dict = record.__dict__.copy()
        print(f"{log_record_dict['filename']} at {log_record_dict['lineno']} within {log_record_dict['funcName']}")


def exc_hook(exc_type, exc_value, tb):
    # 解析traceback获取异常栈帧列表
    tb_frames = traceback.extract_tb(tb)
    # 取最后一个帧(异常实际抛出的位置)
    if tb_frames:
        last_frame = tb_frames[-1]
        filename = os.path.basename(last_frame.filename)
        lineno = last_frame.lineno
        funcname = last_frame.name
    else:
        # 异常栈为空时回退到钩子位置
        filename = os.path.basename(__file__)
        lineno = 25
        funcname = "exc_hook"
    
    # 用extra参数覆盖日志位置信息
    logger.critical(
        "Error happened",
        exc_info=(exc_type, exc_value, tb),
        extra={
            'filename': filename,
            'lineno': lineno,
            'funcName': funcname
        }
    )

修改后运行输出:

log_record_test.py at 12 within test
log_record_test.py at 17 within test2

方案2:用日志过滤器自动处理(更通用)

如果希望所有带exc_info的日志都自动替换位置信息,可以自定义一个日志过滤器,无需修改每个日志调用:

修改后的log_record_setup.py代码:

# File log_record_setup.py
import logging
import traceback
import os

logger = logging.getLogger("TestLogger")


class LogHandler(logging.Handler):
    def __init__(self):
        super(LogHandler, self).__init__()

    def emit(self, record):
        self.format(record)
        log_record_dict = record.__dict__.copy()
        print(f"{log_record_dict['filename']} at {log_record_dict['lineno']} within {log_record_dict['funcName']}")


class ExceptionLocationFilter(logging.Filter):
    def filter(self, record):
        # 检查日志是否携带异常信息
        if record.exc_info is not None:
            _, _, tb = record.exc_info
            tb_frames = traceback.extract_tb(tb)
            if tb_frames:
                last_frame = tb_frames[-1]
                record.filename = os.path.basename(last_frame.filename)
                record.lineno = last_frame.lineno
                record.funcName = last_frame.name
        return True

# 给日志器添加过滤器
logger.addFilter(ExceptionLocationFilter())


def exc_hook(exc_type, exc_value, tb):
    # 保持原有写法即可,过滤器自动处理位置替换
    logger.critical("Error happened", exc_info=(exc_type, exc_value, tb))

这种方式更灵活,所有传入exc_info的日志都会自动替换为异常真实发生的位置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:13:11