iris 默认 logger 是 zerolog 实例,与 zap 类型不兼容,无法通过 app.logger().output() 直接接入;必须用中间件 + context.value 或结构体 receiver 方式注入 *zap.logger,并手动补全耗时、状态码、字段命名(如 http_status)等关键细节以适配日志平台。

直接替换 Iris 默认 logger 不可行——Iris 的 app.Logger() 是 zerolog 实例,和 zap 完全不兼容;必须绕过框架 logger 接口,用中间件 + context 注入方式把 *zap.Logger 传进 handler。
为什么不能用 app.Logger().Output() 接入 Zap
Iris 的 app.Logger() 返回的是 *zerolog.Logger,其底层是 io.Writer + 结构化字段链式构建;Zap 的 *zap.Logger 是完全独立的类型,没有隐式转换路径。调用 app.Logger().Output(zapWriter) 只会把 zap 的输出塞进 zerolog 的 writer 字段,但日志仍走 zerolog 编码逻辑,最终 JSON 字段名、时间格式、level 键全乱,Loki 或 ELK 解析失败。
- zerolog 和 zap 的 encoder 行为不一致:zerolog 默认用
time.Time直接转字符串,zap 默认用EncodeTime函数控制格式 -
app.Logger().With().Str()添加的字段不会透传给 zap,因为两者字段树不共享 - 哪怕强行用
zerolog.ConsoleWriter{Out: zapcore.AddSync(...)},也仅能骗过输出目标,无法获得 zap 的零分配优势
正确注入方式:中间件 + context.Value(临时)或 struct receiver(推荐)
最稳的做法不是“集成”,而是“并行使用”:保留 Iris 默认 logger 打框架启动/错误日志,另起一套 *zap.Logger 专用于业务和请求日志,并通过明确载体传递。
- 在
main()初始化全局*zap.Logger,配置好zap.AddCaller()、zapcore.Lock(zapcore.AddSync(...))、JSON encoder 禁用自定义时间格式 - 写一个中间件,把 logger 带上通用字段(如
service_name、env)后存入context.Context:ctx = context.WithValue(ctx, "zap", logger.With(zap.String("service", "user-api"))) - handler 中显式取出:
logger := ctx.Value("zap").(*zap.Logger),再叠加请求级字段(如path、method) - 更推荐结构体 receiver 注入:定义
type UserHandler struct { logger *zap.Logger },初始化时传入,避免context.WithValue的类型断言开销和 panic 风险
请求日志中间件的关键细节
Iris 没有类似 Gin 的 gin.Context 生命周期钩子,所以耗时、状态码、panic 捕获必须手动补全。
- 记录耗时:在中间件开头记
start := time.Now(),结尾用time.Since(start);别依赖ctx.Values().Get("start"),其他中间件可能覆盖 - 获取真实状态码:必须用
ctx.ResponseWriter().Status(),ctx.GetStatusCode()返回的是当前缓冲区值,可能未定稿 - 别在
ctx.Next()前读ctx.Request().Body,会破坏流,后续 handler 读不到 body - 如果用了
app.Use(iris.Recover),要加 flag 过滤重复 error 日志:比如在 recover 中间件里设ctx.Set("recovered", true),请求日志中间件检查该 key 存在则跳过500记录
字段命名与日志平台兼容性
Zap 默认字段名是小驼峰(httpStatus、reqId),但 Loki、阿里 SLS、Datadog 等默认按 snake_case(http_status、req_id)建索引。不统一就查不到。
- 改
EncoderConfig:config.EncoderConfig.LevelKey = "level"、config.EncoderConfig.TimeKey = "timestamp"、config.EncoderConfig.CallerKey = "caller" - HTTP 相关字段手动转下划线:
zap.String("http_method", r.Method)、zap.Int("http_status", ctx.ResponseWriter().Status()) - client_ip 优先取
ctx.RemoteAddr(),代理场景需解析X-Forwarded-For但要校验可信跳数,别直接信任 header
真正难的不是把 Zap 启起来,而是让每条日志都带对字段、落在对文件、被对系统索引到——字段名大小写、status 获取时机、writer 是否加锁,三个点错一个,线上排查时就少一半线索。











