必须用 gin.New() 替代 gin.Default() 并手动注册自定义 Zap 中间件,初始化时需加锁、禁用默认时间格式、使用 JSON 编码器与 ISO8601 时间格式、启用 AddCaller 与 AddCallerSkip、避免 sampling、正确配置 lumberjack 轮转参数及按 level 分文件的 LevelEnablerFunc。

直接用 gin.Default() 就没法用 Zap,必须弃用默认中间件,自己构造 Logger 和 Recovery 中间件,并把 *zap.Logger 注入进去——否则你写的 Zap 配置根本不会生效。
替换 gin.Default() 为 gin.New() 并手动注册中间件
这是最基础也最容易被跳过的一步。Gin 的 gin.Default() 内部硬编码了 gin.Logger() 和 gin.Recovery(),它们只写到 os.Stdout,和 Zap 完全无关。
- 必须用
gin.New()创建空引擎,再调用engine.Use(yourZapLogger(), yourZapRecovery()) -
yourZapLogger()是自定义中间件函数,类型为gin.HandlerFunc,内部调用logger.Info()或logger.With()记录请求耗时、状态码等 -
yourZapRecovery()同理,但要捕获 panic 并调用logger.Error(),注意别漏掉err和stack字段 - 切忌在中间件里每次调用
logger.With(zap.String("req_id", ...))—— 应该提前在全局 logger 上挂通用字段(如service_name),请求级字段只叠加一次
初始化 zap.Logger 时必须锁住 writer 并禁用时间格式化
不加锁或保留默认时间格式,高并发下要么 panic 要么 GC 暴增,性能直接打回原形。
- 用
zapcore.Lock(zapcore.AddSync(fileWriter))包裹文件 writer,裸写*os.File在多 goroutine 下会 panic - encoder 必须用
zapcore.NewJSONEncoder,且EncodeTime设为zapcore.ISO8601TimeEncoder—— 自定义time.Now().Format(...)会触发堆分配,压测中 GC 压力翻倍 - 务必加
zap.AddCaller(),它走编译期内联,比runtime.Caller快 3–5 倍;同时配zap.AddCallerSkip(1)跳过中间件封装层,否则 caller 总指向中间件函数 - 别用
zap.NewProduction()直接初始化,它默认启用 sampling,高频请求日志(比如每秒百次的健康检查)可能被静默丢弃
用 lumberjack 实现日志轮转时路径和时区必须显式设对
Zap 本身不轮转,轮转行为完全由 lumberjack.Logger 控制;配错一个参数,日志就写飞或归档失效。
-
Filename必须是绝对路径,相对路径在 systemd daemon 或容器中极易写到/或权限不足目录 -
MaxSize单位是 MB,不是字节 —— 设100比1024更稳妥,避免单文件过大拖慢读取 -
MaxAge: 7表示“最老日志文件存活 7 天”,不是“每天生成一个”;如果某天没日志,就不会生成当天文件,MaxAge不触发清理 - 务必设
LocalTime: true,否则文件名用 UTC 时间,在中国服务器上导致归档时间错乱(比如凌晨 1 点生成的文件名却是前一天)
按 level 分文件(error.log / info.log)得用 LevelEnablerFunc 拦截
Zap 不提供开箱即用的 level 分离开关,必须手写 zapcore.LevelEnablerFunc 控制每个 core 的写入逻辑,否则 error 日志进不了 error.log,info 日志又漏掉上下文。
- 不能只靠
core := zapcore.NewCore(..., level)—— 这只是设置 core 默认 level,不拦截其他 level 日志 - 要为
error.log构造一个 core,绑定独立 writer 和 encoder,并用LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel })精确放行 - 同理,
info.log的 core 要用LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl ,否则 error 日志不会出现在 info 文件里,debug 时找不到链路 - 最终用
zapcore.NewTee(core1, core2)合并多个 core,Zap 才会把一条日志分发到不同文件
真正麻烦的不是写几行配置,而是每个环节都得对齐:writer 锁不锁、时间编码格式、lumberjack 路径是否绝对、level 拦截逻辑是否覆盖全路径——漏掉任意一环,线上跑一周后就会发现日志缺失、文件爆炸或 caller 全指向中间件。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











