echo.logger 不能直接用作访问日志中间件,因为它不感知http生命周期、无法获取状态码和响应体大小,且未实现echo.middlewarefunc接口;必须自定义loggingresponsewriter包装responsewriter并覆盖writeheader/write方法。

为什么 echo.Logger 不能直接用作访问日志中间件
因为 echo.Logger 是框架内置的结构化日志器,只负责输出日志内容,不感知 HTTP 请求生命周期(比如无法在响应写入后获取状态码、耗时、响应体大小)。直接把它塞进 echo.Use() 会漏掉关键指标,甚至导致 panic —— 它不是中间件类型。
真正能用的,是实现 echo.MiddlewareFunc 接口的函数。你需要手动 hook c.Response().Writer,拦截写入过程才能拿到最终状态码和字节数。
- 必须包装
http.ResponseWriter,重写WriteHeader()和Write() - 请求开始时间要存到
c.Get()或自定义 context key,不能靠闭包变量(并发下会错乱) - 别在日志里打
c.Request().Body—— 它是一次性流,读了后续 handler 就收不到数据
如何正确包装 ResponseWriter 获取状态码和响应体大小
核心是实现一个自定义的 responseWriter 结构体,嵌入 http.ResponseWriter 并覆盖关键方法。Echo 的 c.Response().Writer 默认是 echo.Response 类型,但你只能通过接口方式安全替换。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
type loggingResponseWriter struct {
echo.ResponseWriter
statusCode int
written int64
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
if lrw.statusCode == 0 {
lrw.statusCode = http.StatusOK
}
n, err := lrw.ResponseWriter.Write(b)
lrw.written += int64(n)
return n, err
}
- 一定要在
WriteHeader()中记录statusCode,否则默认是 0;Write()中才可能触发隐式 200 -
written字段必须是int64,避免大响应体溢出int - 不要调用
lrw.ResponseWriter.Size()—— 它返回的是缓冲区容量,不是实际写出字节数
日志字段取舍:哪些该记,哪些不该记
生产环境高频接口每秒数百请求,日志字段越多,I/O 和磁盘压力越大。别盲目抄 “全量日志” 模板。
- 必记:
c.Request().RemoteAddr(客户端 IP)、c.Request().Method、c.Request().URL.Path、lrw.statusCode、time.Since(start)(纳秒转毫秒)、lrw.written - 慎记:
c.Request().UserAgent()(长度不定,易撑爆行)、c.ParamNames(除非调试路由匹配) - 禁记:
c.Request().Header全量、c.QueryParams()(含敏感参数如 token、手机号) - 如果需要 traceID,从
c.Get("trace_id")取,别硬编码生成 —— 应由上游网关或链路初始化
性能陷阱:JSON 日志 vs 文本日志 + 异步刷盘
用 json.Marshal() 拼日志行?每请求多一次内存分配和序列化,QPS 上千时 GC 压力明显。更糟的是,同步写文件(os.Stdout 或 os.File)会阻塞 goroutine。
- 推荐方案:用
fmt.Sprintf拼接固定格式文本(如"%s %s %s %d %d %s"),字符串拼接比 JSON 快 3–5 倍 - 日志写入务必走
io.Writer抽象,上线前换成带缓冲的bufio.NewWriter,并定期Flush() - 绝对不要在中间件里调
log.Printf—— 它底层锁 stdout,高并发下成瓶颈 - 如果公司已用 Loki/Promtail,可直接输出无结构文本,由采集端做解析;不必强求 Go 层输出 JSON
最常被忽略的一点:日志中间件的耗时统计必须包含 next(c) 调用前后,但不能把 Write() 的耗时算进 handler 时间里 —— 它属于网络栈阶段,和业务逻辑无关。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










