必须将请求日志放在中间件中,因handler无法覆盖404、中间件提前终止等场景;需在next(c)后读取c.Response().Status()和Size(),用可重放Body避免读取失败,并通过context延迟格式化提升性能。

直接在路由 handler 里打日志,看似简单,但会漏掉错误路径、404、中间件提前拦截等场景;真正可靠的请求日志必须放在中间件里,且要避开 time.Since() 位置错误、c.Request().Body 被吃掉、状态码读取时机不对这三类高频问题。
为什么不能只在 handler 里 log.Printf
handler 只在匹配到路由时执行。一旦请求没命中任何路由(比如拼错路径),或被认证中间件 return c.NoContent(http.StatusUnauthorized) 提前终止,handler 根本不运行,日志就断了。Echo 的 echo.HTTPErrorHandler 默认也不记录原始请求信息,只报错。
必须把日志逻辑下沉到中间件链最末端(或至少在 next(c) 之后),才能覆盖所有出口。
中间件中正确获取状态码和响应体大小
c.Response().Status() 和 c.Response().Size() 只有在 next(c) 返回后才可用。常见错误是调用 c.Response().Writer.WriteHeader() 或手动写状态——这会干扰 Echo 内部状态管理,导致重复写头或 panic。
- ✅ 正确:在
next(c)后立刻读c.Response().Status(),它返回的是最终实际写出的状态码 - ✅ 正确:用
c.Response().Size()获取已写入响应体的字节数(注意:未触发写操作时为 0) - ❌ 错误:在
next(c)前读状态码,值恒为 0 - ❌ 错误:调用
c.Response().Writer.WriteHeader(400)后再调用next(c),可能引发http: superfluous response.WriteHeader
如何安全读取并记录 request body
JSON 或表单 body 只能读一次。中间件里用 io.ReadAll(c.Request().Body) 后,下游 c.Bind() 或 c.JSON() 会失败,报错类似 invalid character '}' looking for beginning of value。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
解决方法是用可重放的 Body 包装:
body, _ := io.ReadAll(c.Request().Body)
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
// 记录 body(建议加长度限制,如只取前 1024 字节)
log.Printf("req body: %s", string(body[:min(len(body), 1024)]))
⚠️ 注意:bytes.NewBuffer 是内存 buffer,不适用于大文件上传;生产环境务必加 size check 和 defer c.Request().Body.Close()。
避免日志本身拖慢接口性能
Echo 默认的 echo.Logger 是同步阻塞的,直接往里面塞耗时字段(比如 fmt.Sprintf 拼接长字符串)会在高并发下成为瓶颈。
更轻量的做法是只存关键字段到 context,延迟格式化:
c.Set("log_start", time.Now())
c.Set("log_method", c.Request().Method)
c.Set("log_path", c.Request().URL.Path)
// ... 在中间件末尾
start := c.Get("log_start").(time.Time)
duration := time.Since(start)
log.Printf("%s %s %d %v", c.Request().Method, c.Request().URL.Path, c.Response().Status(), duration)
真正的复杂点在于:你得确保这个日志中间件注册顺序在所有其他中间件之后(比如在 e.Use() 最后调用),否则像 CORS、JWT 验证这类中间件抛错时,你还没来得及记录状态码和耗时。


















