如何实现Python默认风格错误打印并获取完整调用栈信息?
问题场景
有如下抛出异常的函数:
def inner(): raise Exception("message")
在PyCharm控制台运行该函数时,能得到包含runcode层的完整错误回溯:
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevconsole.py", line 364, in runcode
coro = func()
^^^^^^
File "", line 1, in
File "", line 2, in inner
Exception: message
出于日志记录需求,尝试用traceback.format_exc()和sys.exc_info()获取该完整信息,编写了如下代码:
import sys import traceback def inner(): raise Exception("message") def outer(): try: inner() except Exception as e: exc_type, exc_obj, exc_tb = sys.exc_info() print(exc_type) print(exc_obj) print(exc_tb) print(traceback.format_tb(exc_tb)) print(traceback.format_exc())
但运行后仅能获取outer和inner的调用栈,缺失runcode层的上层调用栈信息,需要解决如何获取并记录这部分内容。
解决方案
核心原因
当在outer的except块中捕获异常时,sys.exc_info()返回的追踪对象(exc_tb)仅包含从异常抛出点到当前捕获点的栈帧,上层的runcode等调用栈已退出当前异常追踪链,无法直接获取。
方法1:让异常向上传播到顶层捕获
不在业务函数内捕获异常,而是让异常向上传播到程序顶层再捕获,此时就能拿到包含上层调用栈的完整回溯:
import sys import traceback def inner(): raise Exception("message") def outer(): inner() # 顶层捕获异常 if __name__ == "__main__": try: outer() except Exception as e: print(traceback.format_exc())
在PyCharm控制台执行这段代码,即可输出包含runcode层的完整错误回溯。
方法2:结合inspect模块合并栈信息
如果必须在outer的except块中处理异常,可以使用inspect模块获取当前线程的完整活跃栈帧,再与异常的追踪栈合并:
import sys import traceback import inspect def inner(): raise Exception("message") def outer(): try: inner() except Exception as e: # 获取当前线程的完整栈帧 full_stack = inspect.stack() # 获取异常的追踪栈 exc_type, exc_obj, exc_tb = sys.exc_info() exc_stack = traceback.extract_tb(exc_tb) # 过滤掉当前outer函数及之后的栈帧,避免重复 full_frames = [frame for frame in full_stack if frame.function != 'outer'] # 转换为traceback.FrameSummary格式,方便合并 full_summary = [ traceback.FrameSummary( frame.filename, frame.lineno, frame.function, frame.code_context ) for frame in full_frames ] # 合并完整栈与异常栈 combined_stack = full_summary + exc_stack # 格式化输出完整回溯 print("Traceback (most recent call last):") for frame in combined_stack: print(f" File \"{frame.filename}\", line {frame.lineno}, in {frame.function}") if frame.code_context: print(f" {frame.code_context[0].strip()}") print(f"{exc_type.__name__}: {exc_obj}")
这种方式通过inspect.stack()获取上层调用栈,再与异常自身的追踪栈合并,最终输出包含runcode层的完整错误信息。
内容的提问来源于stack exchange,提问作者paul23

