Go错误处理与日志框架如何配合_Go工程化实践经验

15次阅读

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

Go错误处理与日志框架如何配合_Go工程化实践经验

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 覆盖原始错误,否则堆栈和根本原因丢失
  • 若需附加业务字段(如 ErrorCodeTraceID),定义自定义 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 链断裂

日志里能不能看到错误的完整路径、哪一行触发、属于哪个业务子系统,不取决于你用了什么框架,而取决于 error 是怎么被构造和传递的。很多团队花时间调日志格式,却忽略 error 本身是否“可追溯”——这才是最常被跳过的那层。

text=ZqhQzanResources