必须使用 github.com/pkg/errors 包而非标准库 errors,且需用 fmt.Printf("%+v", err) 才能显示完整调用栈;标准库 errors.Wrap 不支持堆栈追踪。

errors.Wrap 为什么没打出调用栈
直接用 errors.Wrap 包装错误,但 fmt.Printf("%+v", err) 依然只显示最外层错误、没有文件行号和调用链——大概率是没用对包,或者打印方式不对。
- 必须用
github.com/pkg/errors(不是标准库errors),老版本 Go( -
fmt.Println(err)或%v格式化会丢掉堆栈;必须用%+v才能展开调用帧 - 包装时传入的错误本身要是带堆栈的(比如也是用
errors.Wrap或errors.New创建的),否则上游没堆栈,下游再包也没用
Go 1.13+ 怎么兼容 errors.Wrap 的行为
标准库 errors 加了 Unwrap 和 Is/As,但不自动记录堆栈。想保留类似 Wrap 的调试能力,得手动补。
- 用
fmt.Errorf("xxx: %w", err)替代errors.Wrap(err, "xxx"),这是标准写法,支持%+v展开(Go 1.17+) - 若需明确打点堆栈,加一行
err = fmt.Errorf("%w at %s:%d", err, "file.go", 42)—— 不推荐硬编码,但说明原理:堆栈本质是运行时捕获,不是字符串拼的 - 第三方库如
golang.org/x/xerrors(已归档)或github.com/cockroachdb/errors可替代,但引入新依赖前先确认团队是否接受
Wrapping 多次后怎么查原始错误类型
连续套了三四层 errors.Wrap,用 errors.Is 判定底层是不是 os.PathError 却返回 false——常见原因是中间某层用了 errors.New 或字符串拼接,断掉了错误链。
- 务必所有中间错误都用
%w或errors.Wrap,避免errors.New("xxx: " + err.Error())这种写法 -
errors.Is(err, os.ErrNotExist)是安全的,它会递归Unwrap()直到匹配或 nil - 调试时用
errors.Cause(err)(pkg/errors提供)可跳过所有包装,拿到最内层错误,方便类型断言
HTTP handler 里错误包装的典型误用
在 Gin/echo 等框架的 handler 中,对每个业务错误都 errors.Wrap(err, "get user failed"),结果日志里全是重复的“get user failed”,根本看不出哪一行出的问题。
立即学习“go语言免费学习笔记(深入)”;
- 只在**错误产生处**(比如 DB 查询、文件读取)做一次 Wrap,带上具体上下文(如
"query user by id=123"),不要在每层 handler 都包一遍 - handler 最终返回给客户端的错误应脱敏,用
err.Error()或自定义 message,别把%+v结果直接吐出去 - 日志记录时统一用
log.Printf("handler error: %+v", err),确保堆栈完整,但注意敏感字段过滤(如密码、token)


















