gin 的 gin.logger() 不统计延迟极值,因其仅记录单次耗时且无内置聚合能力;需自定义中间件用 sync.rwmutex 维护 top n 慢请求排行榜,并通过 /debug/slow 接口安全暴露,同时校验时钟跳变与超大延迟。

为什么默认日志不显示请求延迟极值
Gin 的 gin.Logger() 中间件只输出基础请求信息(状态码、路径、耗时),但不记录延迟分布或极值。想统计 P99、最大延迟、Top N 慢请求,必须自己捕获 time.Since() 并聚合——它不内置指标收集能力,也不是 Prometheus exporter。
如何在中间件里安全记录每个请求的延迟并存入内存排行榜
用 sync.Map 或带锁的切片维护一个固定大小的延迟排行榜(比如 Top 10),避免高频写入导致 GC 压力或并发冲突:
var slowRequests = struct {
sync.RWMutex
list []slowEntry
}{list: make([]slowEntry, 0, 10)}
type slowEntry struct {
Path string
Duration time.Duration
Time time.Time
}
func slowLogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
dur := time.Since(start)
if dur > 500*time.Millisecond { // 只录慢请求
slowRequests.Lock()
slowRequests.list = append(slowRequests.list, slowEntry{
Path: c.Request.URL.Path,
Duration: dur,
Time: time.Now(),
})
if len(slowRequests.list) > 10 {
slowRequests.list = slowRequests.list[1:]
}
slowRequests.Unlock()
}
}
}
- 别用
map[string]time.Duration存——Key 无法反映请求上下文(如带参数的路径) - 别在中间件里直接打印 Top 列表——高并发下频繁
fmt.Println会阻塞 HTTP 处理 - 排序应在读取时做(比如暴露
/debug/slow接口),不在写入时做
怎样暴露排行榜接口且不拖慢主服务
单独起 goroutine 定期清理过期条目(比如保留最近 5 分钟),并通过 GET /debug/slow 返回 JSON,避免每次请求都锁整个列表:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
func registerSlowDebugRoute(r *gin.Engine) {
r.GET("/debug/slow", func(c *gin.Context) {
slowRequests.RLock()
data := make([]map[string]interface{}, len(slowRequests.list))
for i, e := range slowRequests.list {
data[i] = map[string]interface{}{
"path": e.Path,
"duration": e.Duration.Milliseconds(),
"time": e.Time.Format(time.RFC3339),
}
}
slowRequests.RUnlock()
c.JSON(200, gin.H{"top_slow": data})
})
}
- 用
RWMutex读多写少场景更高效 - 返回前不做排序——前端或调用方按
duration字段自己排;若要服务端排,加一行sort.Slice(data, ...) - 不要把排行榜塞进全局 logger 的
extra字段——它会被格式化成字符串并混入所有日志行,污染可观测性
延迟极值统计容易被忽略的精度陷阱
Go 的 time.Since() 在某些虚拟机或低配容器里可能因系统时钟跳变导致负值或异常大值,需兜底校验:
- 检查
dur ,直接跳过记录(说明时钟回拨) - 设硬上限,比如
if dur > 30*time.Second则截断为30*time.Second,防止单次卡死污染排行榜 - 避免用
time.Now().UnixNano()手动差值——time.Since()内部已优化,且更易读
排行榜本身只是快照,真要分析极值分布,得接 OpenTelemetry 或导出到 Prometheus 配 histogram_quantile()。纯内存 Top N 适合快速定位近期异常,不适合长期趋势归因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










