默认echo.Logger不适合日志收集,因其仅封装log.Logger、不支持结构化字段、无上下文透传能力、无法对接zap/zerolog,且输出纯文本格式,不能被ELK或Loki直接解析。

为什么默认的 echo.Logger 不适合日志收集
因为它是封装了 log.Logger 的简单输出器,不支持结构化字段、无上下文透传能力、不能对接 zap 或 zerolog 等主流日志库,且默认日志格式是纯文本,无法被 ELK 或 Loki 直接解析。
实际部署中,你很快会遇到:日志里找不到请求 ID、无法按 status=500 过滤、trace_id 散落在不同行、错误堆栈被截断——这些问题都源于日志没结构化。
- 必须替换掉
echo.Logger,改用中间件 + 结构化日志实例 - 所有日志写入必须走同一入口(比如
logger.Info()),禁止混用fmt.Println或原生log - HTTP 中间件里要提前生成
request_id并注入到echo.Context的Set()中,后续日志才能带出
用 middleware.RequestID() 和自定义日志中间件串联上下文
Echo 自带的 middleware.RequestID() 只负责生成并写入响应头,不自动挂到日志上下文。你需要手动把它和日志实例绑定。
推荐做法:在日志中间件中从 c.Request().Header.Get("X-Request-ID") 读取,或更可靠地——用 c.Get(middleware.DefaultRequestIDKey) 拿到刚生成的 ID(前提是已注册该中间件)。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 顺序很重要:必须先注册
middleware.RequestID(),再注册你的日志中间件,否则c.Get()返回nil - 别直接把
request_id当字符串拼进日志消息体,要用结构化字段,例如:logger.Info("http request", "method", c.Request().Method, "path", c.Path(), "request_id", reqID) - 如果用了
zap,记得用zap.String("request_id", reqID)而不是zap.Any(),避免类型反射开销
如何让 echo.HTTPErrorHandler 输出结构化错误日志
默认错误处理器只调用 c.Logger.Error(),而这个方法不接收字段参数,也无法获取当前请求上下文里的 request_id,导致错误日志孤立无援。
正确做法是重写 HTTPErrorHandler,从 c 中提取必要信息,再交由你的结构化 logger 输出:
e.HTTPErrorHandler = func(err error, c echo.Context) {
reqID := c.Get(middleware.DefaultRequestIDKey)
logger.Error("http error",
zap.String("request_id", fmt.Sprintf("%v", reqID)),
zap.String("path", c.Request().URL.Path),
zap.String("method", c.Request().Method),
zap.Error(err),
)
// 后续仍需调用 c.JSON 等返回响应
}
- 注意
reqID可能是nil,fmt.Sprintf("%v", reqID)比直接断言更安全 - 不要在错误处理器里 panic 或调用
log.Fatal,这会让整个服务中断 - 如果用了
zerolog,对应写法是logger.Err(err).Str("request_id", ...).Msg("http error")
日志输出到文件 + stdout 双通道时的常见陷阱
本地开发看 stdout 方便,生产环境必须落盘,但直接用两个 io.MultiWriter 往同一个 os.File 写,会导致日志错乱或丢失——因为 zap 等库内部有缓冲,且并发写入无锁保护。
- 正确方式是创建两个独立的 logger 实例:一个配
os.Stdout,一个配os.File,共用同一编码器(如zapcore.JSONEncoder) - 用
zapcore.NewTee组合多个zapcore.Core,而不是靠io.MultiWriter - 文件日志务必配置轮转(如
lumberjack.Logger),否则单文件涨到几十 GB 是常态,且tail -f会卡死
真正难处理的是日志采样和分级:比如只对 ERROR 级别做全量采集,INFO 级别按 1% 采样。这需要在 Core.Check() 阶段介入,不是加个 LevelEnablerFunc 就能搞定的。

















