fmt.Println(err) 看不到行号和调用路径,因为标准 error 接口仅要求 Error() string 方法,原始错误不记录位置;必须用 fmt.Errorf("%w", err) 在源头包装,再以 fmt.Printf("%+v", err) 才能显示文件名、行号及完整堆栈。

Go 调试时的错误信息本身不带调用栈,直接 fmt.Println(err) 会丢失关键上下文 —— 必须用 fmt.Printf("%+v", err) 或启用 errors 包的链式包装才能看到文件名、行号和完整堆栈。
为什么 fmt.Println(err) 看不到行号和调用路径
Go 标准库的 error 接口只要求实现 Error() string 方法,返回纯字符串。默认错误(如 fmt.Errorf("xxx"))不记录位置信息,所以 fmt.Println 只能打印出错误文本,没有堆栈。
- 只有使用
fmt.Errorf("msg: %w", err)并配合%+v才能展开嵌套错误和位置 -
errors.Is()和errors.As()依赖包装结构,不是靠字符串匹配 - 第三方包(如
github.com/pkg/errors)已过时;Go 1.13+ 原生errors包支持%w和%+v,无需额外引入
调试时该用 fmt.Printf("%+v", err) 还是 debug.PrintStack()
%+v 是首选:它只对实现了 fmt.Formatter 的错误(比如用 %w 包装过的)输出带文件/行号的完整链路;debug.PrintStack() 打印的是当前 goroutine 全局堆栈,和错误无关,容易干扰判断。
- 正确做法:
if err != nil { fmt.Printf("failed at %s: %+v\n", "doX", err); return } - 错误做法:在错误处理分支里调用
debug.PrintStack(),它不绑定 err,且无法过滤无关帧 - 注意:
%+v对未包装的原始错误(如os.ErrNotExist)仍只显示字符串,不会“自动加堆栈”
如何让自定义错误也支持 %+v 输出位置信息
手动实现 fmt.Formatter 接口太重;更实用的方式是:在创建错误时就用 fmt.Errorf("%w", originalErr) 或 fmt.Errorf("context: %w", err) 包装,Go 运行时会自动注入调用点。
立即学习“go语言免费学习笔记(深入)”;
- 包装一次即可,不需要层层都包;最外层包装决定
%+v展开深度 - 避免重复包装:
fmt.Errorf("again: %w", fmt.Errorf("inner: %w", err))会导致冗余帧,调试时反而难读 - 如果必须自定义类型,只需嵌入
struct{}+ 实现Unwrap() error,Go 会自动处理格式化逻辑
真正容易被忽略的是:错误是否被包装,取决于你 **第一次用 %w 创建它的位置**,而不是打印时用了什么格式符。没在源头包装,%+v 再怎么调也出不来行号。










