Go 中 error 构造方式决定日志上下文可追溯性:应使用链式包装(%w)、结构化错误类型或自定义 error 实现 Unwrap/LogValue,避免 errors.New 覆盖原始错误,确保日志能提取错误码、堆栈、TraceID 等关键信息。

Go 中 error 包的错误构造方式影响日志上下文传递
直接用 errors.New("xxx") 或 fmt.Errorf("xxx") 生成的 error 缺乏结构化字段,导致日志中无法自动提取错误码、请求 ID、堆栈等关键信息。工程中应优先使用带字段的错误类型(如 pkg/errors 已弃用,推荐 github.com/cockroachdb/errors 或原生 errors.Join/fmt.Errorf("%w") 链式包装)。
- 用
fmt.Errorf("failed to parse config: %w", err)保留原始错误链,日志框架(如zerolog)可通过errors.Unwrap逐层提取 - 避免在中间层用
errors.New覆盖原始错误,否则堆栈和根本原因丢失 - 若需附加业务字段(如
ErrorCode、TraceID),定义自定义 error 类型并实现Error()和Unwrap()方法
zerolog.With().Stack().Err() 不会自动打印 error 堆栈
zerolog 默认不展开 error 的 stack trace,即使调用了 .Stack(),也仅记录当前 goroutine 的调用栈,而非 error 自身携带的 stack(比如由 github.com/cockroachdb/errors 包裹的)。必须显式调用 .Err(err).Stack() 并配合启用 stack capture。
- 初始化 logger 时启用:
log := zerolog.New(os.Stderr).With().Timestamp().Stack().Logger()
- 记录错误时写成:
log.Error().Err(err).Msg("handler failed")—— 注意.Err()必须在.Stack()后调用才有效 - 若用
github.com/cockroachdb/errors,需额外调用errors.Detail(err)获取带堆栈的字符串,再手动注入日志字段
HTTP handler 中 recover panic 后的 error 日志容易丢失 trace 上下文
全局 panic 捕获(如 http.Server.ErrorLog 或中间件中的 recover())拿到的是裸 interface{},不是 error 类型,且原始 request context(含 trace ID、user ID)已不可达。
- panic 恢复后应立即从当前 goroutine 的 context(如有)或 HTTP header(如
X-Request-ID)中提取 trace 信息 - 不要直接
log.Error().Interface("panic", r).Msg("recovered"),而应转换为 error:if p := recover(); p != nil { var err error if e, ok := p.(error); ok { err = e } else { err = fmt.Errorf("%v", p) } log.Error().Err(err).Str("panic_type", fmt.Sprintf("%T", p)).Msg("panic recovered") } - panic 日志级别建议设为
LevelFatal或打标"panic": true字段,便于告警过滤
zap 与 slog 在 Go 1.21+ 下的 error 日志兼容性差异
slog(标准库)默认不解析 error 链,zap 则通过 zap.Error() 自动展开;但两者对自定义 error 的字段提取逻辑不同,混用时易漏关键信息。
-
slog记录 error 推荐用slog.Any("err", err),而非slog.String("err", err.Error()),前者会触发LogValue()方法(若 error 实现了该接口) -
zap中用zap.NamedError("err", err)可保留 error 名称(如*json.SyntaxError),比zap.Error(err)更利于分类 - 跨模块传递 error 时,统一用
fmt.Errorf("mod: %w", err),避免在某一层转成字符串再包一次,否则 error 链断裂










