因为默认echo.HTTPErrorHandler在panic恢复后才调用,此时request context已被重置,注入的带trace_id的logger不可达;正确做法是中间件中将logger存入c.Request.Context(),error handler内显式从中取出。

为什么直接用 echo.HTTPErrorHandler 会丢 trace_id 字段
因为默认的 echo.HTTPErrorHandler 是 panic 恢复后才调用的,此时 request context 已被重置或销毁,之前通过 c.Set("logger", ...) 注入的带 trace_id 的 logger 实例已不可达。你看到的日志里只有 http_error 和 status,但 trace_id、path、method 全没了。
正确做法是:在中间件中提前把 logger 塞进 c.Request.Context(),并在 error handler 内部显式从 context 取出 logger,而不是依赖闭包或中间件局部变量:
- 中间件里调
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "logger", reqLogger)) - 自定义
echo.HTTPErrorHandler时,用c.Request.Context().Value("logger").(*zap.Logger)取实例 - 切忌在 handler 里用
c.Get("logger")—— Echo 的c.Get是基于 echo.Context 的 map,和 http.Request.Context 是两套生命周期
logger.With() 调多少次才算过载
每次调 logger.With() 都会新建一个 *zap.Logger 实例,底层是字段 slice 的拷贝+追加,高频请求下 allocs/op 明显上升。压测发现单个 handler 内调 3 次 With(),QPS 下降约 12%,GC 压力也变大。
推荐做法是「一次构造,全程复用」:
- 中间件中提取
X-Trace-ID,为空则生成xid.New().String(),仅调一次logger.With(zap.String("trace_id", tid)) - 把结果存到
c.Request.Context()或c.Set("logger", ...)(注意:后者只在当前 echo.Context 生命周期有效) - 后续 handler、validator、error handler 全部复用这个实例,不再重复
With() - 通用字段(如
service、env)应在启动时就预置进 root logger,避免每个请求都叠加
写文件时 lumberjack.Logger 配错导致日志截断
进程退出时最后一段日志没刷到磁盘,常见原因是 lumberjack.Logger 没配 LocalTime: true 或没做 Sync(),更隐蔽的是:你用了 zapcore.NewTee 做多目标输出(比如同时写文件+stdout),但没给每个 writer 单独包装 zapcore.AddSync。
关键配置项必须显式设:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
-
MaxSize: 100(单位 MB,不是字节) -
MaxBackups: 7(保留最近 7 个归档) -
MaxAge: 7(归档最多存 7 天) -
LocalTime: true(否则归档文件名是 UTC 时间,排查时易混淆) - 启动前执行
os.MkdirAll(filepath.Dir(logPath), 0755),否则静默失败
最后别忘了在 main() 退出前调一次 logger.Sync(),否则缓冲区内容可能丢失。
字段命名不一致让 Kibana 查不到 status:500
Zap 默认字段名是小驼峰(httpStatus、reqId),但 Kibana/Loki/阿里 SLS 等平台默认按蛇形(http_status、req_id)建索引。你写了 zap.Int("http_status", 500),Zap 会自动转成 httpStatus,结果字段根本没被索引,搜 status:500 永远为空。
解决方法只有两个,且必须统一:
- 要么全局禁用 Zap 的字段名自动转换:初始化时用
zap.NewDevelopmentConfig().EncoderConfig.NameKey = "name"等手动控制(不推荐) - 要么严格遵守平台约定:所有字段名手写蛇形,例如
zap.Int("http_status", c.Response.Status)、zap.String("req_id", tid) - 特别注意:Gin/Echo 官方 zap 中间件(如
ginzap.Logger())默认也走小驼峰,得自己 patch 或换用自定义版本
这个点上线前不核对字段映射表,线上查问题时会浪费至少一小时。










