errtrace.Wrap必须放在return前,仅对即将返回给调用方的错误生效;若在非return处调用会污染返回路径树且浪费性能,多return点需逐一包装。

errtrace.Wrap 必须放在 return 前,不是所有 err 都该包装
errtrace 不是“在每个 error 出现的地方都调用 errtrace.Wrap”,它只对**即将返回给调用方的错误**生效。如果在中间步骤对 err 进行包装但没 return(比如只用于日志打印或条件判断),不仅浪费性能,还会污染返回路径树——errtrace 会把它当作一次真实传播节点记录下来。
- ✅ 正确位置:仅在
return err前一行使用return errtrace.Wrap(err) - ❌ 错误习惯:在
if err != nil { log.Printf(...); errtrace.Wrap(err) }里单独调用,此时包装后的 error 没被返回,后续调用链无法感知 - ⚠️ 注意:若函数有多个 return 点(如 early return + 正常 return),每个 return err 的地方都要单独 wrap,不能只包一次
multierror.Append 要配合 %w 和 WithIncludeStack(true)
用 multierror.Append 聚合多个错误时,不加配置默认只拼字符串,原始堆栈全丢。你看到的 "failed to A: failed to B" 里根本找不到 B 是哪一行 dial 失败的。
- 必须用
fmt.Errorf("context: %w", err)包装底层 error,不能用sprintf或%s - 初始化 multierror 时要显式启用栈保留:
multierror.WithIncludeStack(true)或设置multierror.FormatFunc - 避免和
errors.Join混用:后者返回的类型不实现multierror.Error接口,一旦上游用 multierror、下游用 Join,整个错误链的StackTrace()就失效
Yuxi 或 pkg/errors 的 %+v 输出依赖 runtime.Caller
像 github.com/pkg/errors 或 Yuxi 这类库靠 runtime.Caller 捕获当前调用点,但这个开销在高并发场景下不可忽视——每调用一次 Wrap 就触发一次 PC 查找,QPS 上万的服务可能因此增加 5%~10% CPU 占用。
- 开发/测试环境可放心用
%+v查堆栈;生产环境建议关闭详细堆栈输出,改用结构化日志字段(如"file","line")替代 - 不要在循环体或 hot path(如 HTTP 请求 body 解析)里频繁调用
Wrap,优先用哨兵错误或预定义 error 变量 - Yuxi 的上下文键值对(
.WithField("user_id", 123))虽好,但序列化进 error 对象后,若没配套的日志解析器,这些字段就只是字符串里的噪声
OpenTelemetry 的 span.RecordError 无法识别 multierror 结构
span.RecordError(err) 在 OpenTelemetry Go SDK 中只认标准 error 接口,遇到 *multierror.Error 会直接 fallback 到 err.Error() 字符串,丢失所有子错误位置和分类信息。
立即学习“go语言免费学习笔记(深入)”;
- 必须先调用
multierror.Flatten(err)把它转成单个 error(通常是第一个非-nil 子错误),再传给RecordError - 若需上报全部子错误,得手动遍历
err.Errors(),为每个子 error 创建独立 span 标签(如error.0.type,error.1.stack) - gRPC 场景更麻烦:
status.FromError完全不认识 multierror,不 flatten 就只能得到codes.Unknown,链路追踪里看不到真实失败节点


















