必须通过 app.Logger().SetChild() 注入封装 zap.Logger 的 wrapper 实例,因 iris.Logger 底层依赖 io.Writer 而 zap.Logger 不满足该接口,且 SetChild() 要求参数实现 iris.Logger 接口,需用结构体包装 *zap.SugaredLogger 并实现 Infof 等方法。

不能直接用 iris.Logger() 替换为 zap.Logger,必须通过 app.Logger().SetChild() 注入封装后的 zap.Logger 实例,否则日志会丢失、panic 或无法生效。
为什么 iris.Logger().SetOutput() 对 zap 无效
iris.Logger() 底层是 log.Logger,只接受 io.Writer;而 zap.Logger 不是 io.Writer,不能直接塞给 SetOutput()。硬传会导致编译失败或运行时 panic(如 cannot use logger (type *zap.Logger) as type io.Writer)。
常见错误写法:
app.Logger().SetOutput(zapLogger) // ❌ 编译报错
正确路径是:保留 iris 日志器外壳,把 zap 实例“挂载”为子 logger,由 iris 在内部桥接调用。
- 必须用
app.Logger().SetChild()(v12+),不是旧版SetOutput() - 传入的必须是实现了
iris.Logger接口的 wrapper,不能是裸*zap.Logger - iris v12.2.5 中
SetChild()要求参数类型为iris.Logger,需手动适配
如何封装 zap.Logger 满足 iris.Logger 接口
iris 的 iris.Logger 接口包含 Infof、Warnf、Errorf 等方法,需用结构体包装 *zap.Logger 并实现这些方法,转发到 zap 的 Sugar()。
示例封装(精简版):
type ZapLogger struct {
sugar *zap.SugaredLogger
}
func (z *ZapLogger) Infof(format string, args ...interface{}) {
z.sugar.Infof(format, args...)
}
func (z *ZapLogger) Warnf(format string, args ...interface{}) {
z.sugar.Warnf(format, args...)
}
func (z *ZapLogger) Errorf(format string, args ...interface{}) {
z.sugar.Errorf(format, args...)
}
// 其他方法同理(Debugf、Fatalf 等)
初始化后注入:
zapLogger, _ := zap.NewProduction()
defer zapLogger.Sync()
wrapper := &ZapLogger{zapLogger.Sugar()}
app.Logger().SetChild(wrapper)
- 务必用
Sugar(),避免每次调用都 new encoder;Logger原生接口不兼容printf风格 - 不要在 handler 里重复创建 wrapper 实例——全局复用一个即可
- 若需请求级字段(如
req_id),应在 middleware 中用logger.With(...)创建新Sugar,再存入 context
文件轮转和多级分文件必须靠 lumberjack + 多 core
iris 不干预日志写入目标,但 zap 本身也不管轮转——所有归档逻辑都在 io.Writer 层。要实现 error.log / info.log 分离 + 自动切割,得组合 lumberjack.Logger 和 zapcore.NewTee()。
关键配置点:
-
lumberjack.Logger.Filename必须为绝对路径(如/var/log/myapp/error.log),相对路径在 daemon 下常写入根目录 - 两个 writer:一个给 error core(
LevelEnablerFunc返回level >= ErrorLevel),一个给 info core(level >= InfoLevel) - 用
zapcore.NewTee(infoCore, errorCore)合并,确保 error 日志既进error.log也进info.log(保留上下文) -
lumberjack.MaxAge: 7和MaxBackups: 30需配合使用,单设MaxAge在低频写入时可能不触发归档
注意:lumberjack 的 LocalTime: true 必须显式开启,否则在中国服务器上生成的归档文件名时间戳是 UTC,导致按天切片错乱。
容易被忽略的 sync 和字段命名问题
进程退出前不调 zapLogger.Sync(),最后几条日志大概率丢失——iris 不负责帮你 flush zap 的缓冲区。
另一个隐形坑是字段名:zap 默认用小驼峰(httpStatus),但 ELK、Loki、阿里 SLS 等平台默认索引下划线命名(http_status)。字段名不一致 → Kibana 查不到 status:500 日志。
- 别手动在每个
logger.Info()里转字段名,改用自定义EncoderConfig统一映射 - 测试环境用
zap.NewDevelopment()时,禁用EncodeCaller或改用ShortCallerEncoder,否则容器里颜色字符破坏 JSON 结构 - 高并发场景下,避免在 middleware 里对每个请求都调
logger.With(zap.String("req_id", ...))——预置通用字段(service_name,env)更高效


















