必须在中间件末尾显式检查c.Writer.Status()≥400或c.Errors非空,并用Prometheus Counter按status_code、method、path打标统计;需归一化path、区分3xx重定向、捕获c.IsAborted()及panic恢复场景,方能准确反映链路异常真实水位。

如何在Gin中间件里捕获并统计链路异常
直接在请求生命周期中埋点,是获取真实异常指标的唯一可靠方式。Gin的Use和Next机制天然适合做这件事——不能依赖下游返回后再补统计,因为panic、超时中断、中间件提前c.Abort()等情况都会绕过后续逻辑。
关键动作:在中间件末尾检查c.Writer.Status()是否为 0(未写入)或属于 4xx/5xx 范围,并结合c.Errors判断是否有未处理错误。
- 必须在
Next()之后读取状态码,否则永远是 0 -
c.Errors会累积所有c.Error()调用,包括gin.ErrorTypePrivate类内部错误,需过滤掉非业务错误 - 避免在
defer里统计,因panic可能使defer执行顺序不可控,推荐统一在中间件末尾显式判断
用Prometheus Counter记录HTTP错误率
不要自己维护map+mutex计数器,直接用prometheus.NewCounterVec按status_code、method、path打标,这是实时性和聚合查询的基础。
示例注册方式:
var (
httpErrorCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_errors_total",
Help: "Total number of HTTP errors by status code",
},
[]string{"status_code", "method", "path"},
)
)
func init() {
prometheus.MustRegister(httpErrorCounter)
}
在中间件中更新:
status := c.Writer.Status()
if status >= 400 {
path := c.Request.URL.Path
method := c.Request.Method
httpErrorCounter.WithLabelValues(strconv.Itoa(status), method, path).Inc()
}
- 注意
path应做归一化(如将/user/123转为/user/:id),否则标签爆炸 - 别漏掉重定向(3xx)——它们不是错误,但若大量出现可能预示路由配置问题,建议单独用
http_redirects_total指标跟踪 - 如果用了
gin.Recovery(),它会吞掉panic并返回500,此时c.Writer.Status()仍是200,必须靠c.Errors.Len() > 0 && c.Errors.Last().Err != nil辅助判断
Gin里怎么识别“链路级”异常而非单次HTTP错误
真正的链路异常,比如下游gRPC调用失败、Redis连接超时、消息队列投递中断——这些不会直接反映在HTTP状态码上,但会显著拉高P95延迟并导致业务逻辑降级。这类指标必须从Span或上下文里提取。
如果你已集成OpenTelemetry:
- 在
otel.Tracer.Start()创建的ctx中,通过span.Status().Code判断是否为codes.Error - 用
span.RecordError(err)显式上报错误,避免只靠span.End()自动推断 - 不要在每个HTTP handler里重复Start Span,应在最外层中间件统一注入,否则父子Span关系断裂
如果没有OTel,至少把关键依赖调用的错误类型记进c.Set("dep_error", "redis_timeout"),再由统一中间件汇总到service_dependency_errors_total{dep="redis", error_type="timeout"}。
为什么你看到的错误率总比APM低?
因为Gin默认不记录被c.Abort()截断的请求,也不统计net/http底层连接关闭、客户端主动断连等场景。这些在链路追踪里常表现为“无Span结束”或“Span状态为Unset”,但在Prometheus指标里完全消失。
- 启用
gin.LoggerWithConfig并设置SkipPaths为空,确保所有路径都走日志中间件,从中提取status和latency - 对
c.IsAborted()为true的请求,强制记录一次http_aborted_requests_total{reason="auth_failed"},理由可从c.Keys或自定义上下文字段获取 - 监控
http_request_duration_seconds_count与http_requests_total的比值,若持续低于0.95,说明有大量请求没走到指标统计点,大概率是abort或panic早于指标埋点
链路异常的真实水位,永远藏在那些没生成完整Span、没走到Writer.WriteHeader、甚至没进入Gin路由匹配的请求里。盯住aborted和recovered panic这两类指标,比盯着500更接近系统脆弱点。


















