Golang如何实现错误日志收集_Golang log与error整合使用方法

应使用结构化日志库(如 zap)并传入 error 类型字段,配合 %w 包装保留错误链,开启 caller 和 stacktrace,避免字符串拼接或手动调用 err.Error()。

Go 的 log 包本身不处理错误,只是输出字符串

很多人以为 log.Println(err) 就算“记录了错误”,其实它只是把 err.Error() 转成字符串打印,丢失了原始 error 类型、堆栈、上下文等关键信息。真正要收集可排查的错误日志,必须让错误对象本身参与日志流程。

推荐做法是:用结构化日志库(如 zapzerolog)替代原生 log,并统一用 error 类型字段记录错误。例如:

logger.Error("failed to open config file", zap.Error(err), zap.String("path", cfgPath))

这样日志系统才能识别该字段为错误类型,后续做告警、聚合、堆栈展开才有效。

fmt.Errorf + %w 保留错误链,别用 fmt.Sprintf 拼接

错误日志失去可追溯性,往往是因为在层层包装时用了字符串拼接,切断了错误链。比如:

  • ❌ 错误写法:err = fmt.Errorf("failed to parse JSON: %s", err.Error()) —— %w 缺失,errors.Is/errors.As 失效
  • ✅ 正确写法:err = fmt.Errorf("failed to parse JSON: %w", err) —— 保留原始错误,支持向下断言和堆栈追踪

尤其在中间件、handler、数据库调用等场景,每层都应使用 %w 包装,否则 logzap 即使记录了最外层错误,也无法还原底层根本原因。

避免在日志里重复调用 err.Error(),交给日志库处理

常见反模式:logger.Info("operation failed", zap.String("error", err.Error()))。这不仅冗余,还可能因 errnil 导致 panic(某些自定义 error 实现未防 nil),也丢失了错误类型语义。

正确方式是直接传 err 值(前提是日志库支持):

logger.Error("db query failed", zap.Error(err), zap.String("sql", query))

如果非要用原生 log 且必须带错误,至少用 fmt.Printf("%+v", err) 获取带堆栈的格式(需 github.com/pkg/errors 或 Go 1.13+ 的 errors 包配合),但依然不如结构化日志可靠。

生产环境必须加 callerstacktrace 配置

没有调用位置和堆栈的错误日志等于废日志。以 zap 为例,初始化时务必开启:

  • z

    ap.AddCaller()
    —— 记录 file:line
  • zap.AddStacktrace(zapcore.ErrorLevel) —— 在 Error 级别自动附加堆栈(注意:仅对 zap.Error(err) 生效,不是所有字段)
  • 避免用 zap.String("stack", debug.Stack()) 手动抓——性能差、易误触发、不精准

另外,log.SetFlags(log.Lshortfile | log.LstdFlags) 对原生 log 仅加文件行号,不带堆栈,排查深层错误基本没用。

错误日志的价值不在“有没有”,而在“能不能顺藤摸瓜定位到第 3 层 goroutine 里那个被忽略的 rows.Err()”。堆栈、调用链、结构化字段,缺一不可。