主题
14 · 结构化日志 Zap
目标:用 JSON 字段日志替代
fmt.Println,方便检索与对接采集。
前置:13 · Viper
文档:uber-go/zap · 对照:pino/winston
1. 为何结构化
text
坏: error: something failed
好: {"level":"error","msg":"create note","requestId":"...","err":"...","userId":"u1"}1
2
2
生产里靠 requestId / userId / path 过滤;纯字符串日志在 ELK/Loki 里难查。
2. 最小可跑
go
package main
import (
"go.uber.org/zap"
)
func main() {
logger, err := zap.NewProduction() // 开发可用 NewDevelopment()
if err != nil {
panic(err)
}
defer logger.Sync() // 忽略 sync 错误即可入门
sugar := logger.Sugar()
sugar.Infow("server start", "port", 8080)
sugar.Errorw("create note failed", "err", "duplicate", "noteId", "n1")
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gin 中间件示例:
go
func ZapLogger(log *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
log.Info("http",
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("latency", time.Since(start)),
zap.String("requestId", c.GetString("requestId")),
)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
3. 级别与字段约定
| Level | 用途 |
|---|---|
| Debug | 本地细节 |
| Info | 正常请求、生命周期 |
| Warn | 可恢复异常(重试成功前) |
| Error | 失败需关注 |
| Fatal/Panic | 极少;进程起不来才用 |
不要把整段 body、密码、token 打进日志。
4. 动手清单
- [ ] 替换项目里所有业务
fmt.Println - [ ] 请求日志带
requestId(接 12 篇中间件) - [ ]
mode=debug用 Development,release用 Production(Viper 驱动) - [ ] 错误用
zap.Error(err),不要只err.Error()丢栈信息习惯(按需)
5. 项目驱动
| 场景 | 字段 |
|---|---|
| 登录失败 | reason(勿打密码) |
| 上传失败 | key、size、err |
| 慢查询预留 | 以后接 GORM 回调打 latency |
6. 常见坑 + AI 审查
| 坑 | 说明 |
|---|---|
每请求 NewProduction() | 应全局单例注入 |
Printf 风格拼字符串 | 失去字段索引 |
| AI 用 logrus/zap 混用两套 | 统一一种 |
