Go 1.13起应使用fmt.Errorf搭配%w动词包装错误以保留原始错误类型和堆栈,避免用%s拼接导致errors.Is/As失效;多错误并行失败用errors.Join聚合;需定位源头时可结合runtime.Caller注入位置信息,但避免中间层冗余包装。

用 fmt.Errorf 包裹错误并添加上下文
Go 1.13 引入了错误包装机制,fmt.Errorf 支持 %w 动词来保留原始错误。这是最常用、最直接的方式:在出错位置补充说明性文字,同时不丢失底层错误类型和堆栈线索。
常见错误是只用 %s 拼接字符串,导致原始错误被转成字符串丢弃——后续无法用 errors.Is 或 errors.As 判断或提取。
err := doSomething()
if err != nil {
// ❌ 错误:丢失原始错误
return fmt.Errorf("failed to process item: %s", err)
// ✅ 正确:用 %w 包装,保留可检视性
return fmt.Errorf("failed to process item: %w", err)
}
-
%w只能出现在格式字符串的最后一个动词,且只能对应一个错误值 - 如果需要多个上下文字段(如 ID、路径),放在
%w前面,用普通字符串插值 - 被包装的错误必须是非 nil;否则
fmt.Errorf("... %w", nil)返回 nil,容易引发空指针
用 errors.Join 合并多个错误上下文
当一次操作可能触发多个独立失败(比如批量写入多个文件),需要用 errors.Join 聚合它们,并保持各自上下文。它返回一个实现了 Unwrap() 的复合错误,支持遍历所有子错误。
注意:它不替代 %w,而是处理“并行失败”场景,不是“链式因果”场景。
立即学习“go语言免费学习笔记(深入)”;
var errs []error
for _, f := range files {
if err := os.WriteFile(f, data, 0644); err != nil {
errs = append(errs, fmt.Errorf("write %s: %w", f, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
-
errors.Join忽略 nil 元素,安全传入含空错误的切片 - 返回的错误不能用
errors.Is精确匹配单个子错误(需遍历errors.Unwrap或用errors.Is配合自定义检查) -
标准库日志不会自动展开复合错误,需手动调用
errors.Unwrap或用第三方包如github.com/cockroachdb/errors
用 fmt.Errorf + runtime.Caller 补充调用位置
标准 %w 不带文件/行号信息。若需快速定位错误源头(尤其在封装多次后),可在包装时手动注入位置:
func wrapWithLocation(err error, msg string) error {
if err == nil {
return nil
}
_, file, line, _ := runtime.Caller(1)
return fmt.Errorf("%s [%s:%d]: %w", msg, filepath.Base(file), line, err)
}
// 使用
return wrapWithLocation(io.ErrUnexpectedEOF, "reading header")
- 不要在热路径频繁调用
runtime.Caller,有明显性能开销(纳秒级变微秒级) - 生产环境建议仅对关键错误或调试模式启用
- 注意
filepath.Base(file)避免打印过长绝对路径,干扰日志可读性
避免在中间层重复包装同一错误
多个函数层层 fmt.Errorf("... %w", err) 会导致错误消息冗余、嵌套过深,最终日志里出现类似 “failed to serve → failed to handle request → failed to decode JSON → invalid character …” 的重复前缀。
真正需要的是:**最外层一次清晰上下文 + 最内层原始错误细节**。中间层应只做必要判断,不无意义包装。
- 如果只是透传错误(没新增语义信息),直接
return err - 如果做了重试、降级、转换等逻辑,再包装;否则留到顶层统一处理
- HTTP handler 等入口处最适合加业务上下文(如
"handling POST /api/v1/users: %w"),而不是每个工具函数都加
错误上下文不是越多越好,关键是让排查者一眼看出“在哪、为什么、原始原因是什么”。过度包装反而掩盖重点。










