应使用 sync/atomic.AddInt64 对包级 int64 变量原子累加,避免全局变量非线程安全;按路径统计用 sync.Map;中间件须注册在目标路由组(如 api.Group("/api"))而非全局,且计数逻辑置于 next(c) 前以确保请求进入即统计。

中间件里用原子计数器累加调用次数
直接在中间件中对全局变量做 ++ 是线程不安全的,Echo 运行在多 goroutine 环境下,必须用 sync/atomic。定义一个 int64 类型的计数器,每次进入中间件就调用 atomic.AddInt64。
- 不要用普通
int+mutex,原子操作更轻量且无锁 - 计数器需声明为包级变量(如
var totalCalls int64),否则每次中间件执行都是新副本 - 如果按接口路径分别统计,建议用
sync.Map存map[string]int64,key 为c.Request().URL.Path
注册中间件时注意执行顺序和路径匹配
Echo 中间件默认作用于所有路由,但若只统计特定接口,得在 e.Group() 或单个 GET/POST 路由上调用 Use(),否则健康检查、静态资源等也会被计入。
- 错误写法:
e.Use(countMiddleware)—— 全局生效,含/favicon.ico、/metrics等干扰项 - 推荐写法:
api := e.Group("/api"); api.Use(countMiddleware); api.GET("/users", handler) - 若用
e.Any()或通配路由,需在中间件内手动过滤方法或路径,例如跳过OPTIONS预检请求
避免在中间件里读写响应体导致统计失真
有些中间件会调用 c.Response().Writer 包装或捕获响应体(比如日志记录、压缩),这会影响 http.ResponseWriter 的原始状态,进而让后续中间件或 handler 对 WriteHeader 的判断出错——但计数本身不受影响;真正的问题是:若你在中间件里提前 WriteHeader(200) 或 panic,可能导致 handler 没执行但计数已+1。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 计数逻辑务必放在中间件最开头(即
next(c)之前),保证“只要请求进来了就算一次” - 不要在
next(c)后再计数,否则失败请求(如 404、500)不会被统计 - 如果要区分成功/失败调用,应在
next(c)后读取c.Response().Status,但注意该值在写入前为 0
导出计数结果时注意并发读取与数据格式
通过 HTTP 接口暴露统计值(如 GET /metrics)时,读取 atomic.LoadInt64(&totalCalls) 是安全的,但若返回 JSON,别直接拼接字符串——用 json.NewEncoder(c.Response()).Encode(),避免因结构体字段未导出或类型不匹配导致空响应。
- 路径建议避开业务路由前缀,例如用
/debug/counts而非/api/counts,防止被统计自身 - 如果用了
sync.Map存路径维度数据,遍历需用Range(),不能直接转 map(Go 1.21+ 支持Map.Clone(),但旧版本仍需手动复制) - 高 QPS 场景下,频繁读取全量计数可能成为瓶颈,可考虑采样或异步聚合
计数本身很简单,难的是确保它不干扰原有流程、不漏不重、不拖慢主链路——尤其是路径分组、错误分支、预检请求这些边缘情况,很容易被忽略。

















