errors.wrap 比 fmt.errorf 多一层堆栈但默认不显示,需用 %+v 或 errors.print 才展开;对外响应用 err.error() 防泄露,服务端日志用 %+v 追溯;优先用 errors.wrap 而非 withstack,避免空字符串或语义不清的描述;go 1.13+ 原生无堆栈捕获能力,pkg/errors 仍不可替代。

为什么 errors.Wrap 比 fmt.Errorf 多一层堆栈但没显示出来?
因为 pkg/errors 默认不自动打印堆栈,只在你显式调用 errors.Print 或用 %+v 格式化时才展开。很多人写了 errors.Wrap(err, "failed to open file"),然后用 log.Println(err),结果只看到错误消息,堆栈消失得干干净净。
- 正确做法:日志输出时用
fmt.Printf("%+v", err),%+v是触发堆栈的关键 - 如果用
log包,得包装一层:log.Printf("%+v", err),直接log.Println(err)会丢堆栈 -
errors.Cause(err)可以拿到最底层原始 error,适合做类型判断或重试逻辑,但别误以为它“恢复”了堆栈——它只是剥离包装
在 HTTP handler 里怎么安全地透传错误堆栈又不泄露给前端?
直接把 %+v 输出的带堆栈 error 返回给客户端,等于把内部路径、函数名、甚至变量值全暴露出去,非常危险。
- 对外响应只用
err.Error()(即%v),它只输出错误文本,不带文件行号和调用链 - 服务端日志才用
%+v,确保可追溯;建议统一封装一个logError(req *http.Request, err error)函数处理 - 避免在
Wrap时塞敏感信息,比如errors.Wrap(err, "user "+u.Email+" failed auth")—— 邮箱可能进日志
errors.WithStack 和 errors.Wrap 到底该用哪个?
绝大多数情况用 errors.Wrap 就够了。errors.WithStack 是个“裸堆栈注入”,不带上下文描述,容易让后续维护者看不懂这层堆栈存在的意义。
-
errors.Wrap(io.ErrUnexpectedEOF, "reading config file")—— 推荐:有原因、有位置感 -
errors.WithStack(io.ErrUnexpectedEOF)—— 少用:堆栈存在,但没人知道为什么加这一层 - 注意:
Wrap的第二个参数不能为空字符串,否则会 panic;也不建议只写动词如"opening",要完整语义如"opening database connection"
升级到 Go 1.13+ 后,pkg/errors 还值得继续用吗?
Go 原生 errors.Is/errors.As 解决了错误判等和解包问题,但原生仍**不提供堆栈捕获能力**——fmt.Errorf("...: %w", err) 不记录调用点,只保留底层 error。
立即学习“go语言免费学习笔记(深入)”;
- 如果你只需要错误分类和简单包装,原生
%w足够,也更轻量 - 但只要需要定位“到底在哪一行代码出的问题”,
pkg/errors的Wrap/%+v仍是事实标准 - 混用风险:不要对同一个 error 既用
pkg/errors.Wrap又用fmt.Errorf(... %w),会导致堆栈重复或丢失
Wrap 都得回答“这里发生了什么”,而不是“我又调了一次函数”。漏掉这个意识,再全的堆栈也救不了排查效率。











