From 67e78f2c4452a6452a4074a990ef2bde78f4c84c Mon Sep 17 00:00:00 2001 From: fengsilin Date: Wed, 20 May 2026 18:10:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(metrics):=20Gin=20=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=20-=20=E6=B4=BB=E8=B7=83=E8=AF=B7=E6=B1=82=E3=80=81?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=E3=80=81=E7=8A=B6=E6=80=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/metrics/metrics_test.go | 85 ++++++++++++++++++++++++++++++++++ common/metrics/middleware.go | 28 +++++++++++ 2 files changed, 113 insertions(+) create mode 100644 common/metrics/middleware.go diff --git a/common/metrics/metrics_test.go b/common/metrics/metrics_test.go index 1736d7f..ad09b42 100644 --- a/common/metrics/metrics_test.go +++ b/common/metrics/metrics_test.go @@ -1,8 +1,11 @@ package metrics import ( + "net/http/httptest" "testing" + "time" + "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" @@ -121,3 +124,85 @@ func TestMetricsDisabledIsNoOp(t *testing.T) { m.ActiveRequests.WithLabelValues("gpt-4").Inc() m.RequestDuration.WithLabelValues("1", "gpt-4", "true", "chat").Observe(1.0) } + +func TestMetricsMiddleware_ActiveRequests(t *testing.T) { + gin.SetMode(gin.TestMode) + reg := newTestRegistry() + m := NewMetrics(reg) + + r := gin.New() + r.Use(MetricsMiddleware(m)) + + blockCh := make(chan struct{}) + r.GET("/test", func(c *gin.Context) { + <-blockCh + c.Status(200) + }) + + go func() { + req := httptest.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + }() + go func() { + req := httptest.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + }() + + time.Sleep(100 * time.Millisecond) + + assert.Equal(t, float64(2), testutil.ToFloat64(m.ActiveRequests.WithLabelValues(""))) + + close(blockCh) + time.Sleep(100 * time.Millisecond) + + assert.Equal(t, float64(0), testutil.ToFloat64(m.ActiveRequests.WithLabelValues(""))) +} + +func TestMetricsMiddleware_RequestsTotalByCode(t *testing.T) { + gin.SetMode(gin.TestMode) + reg := newTestRegistry() + m := NewMetrics(reg) + + r := gin.New() + r.Use(MetricsMiddleware(m)) + r.GET("/200", func(c *gin.Context) { c.Status(200) }) + r.GET("/400", func(c *gin.Context) { c.Status(400) }) + r.GET("/500", func(c *gin.Context) { c.Status(500) }) + + for i := 0; i < 3; i++ { + req := httptest.NewRequest("GET", "/200", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + } + req := httptest.NewRequest("GET", "/400", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + req = httptest.NewRequest("GET", "/500", nil) + w = httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, float64(3), testutil.ToFloat64(m.RequestsTotal.WithLabelValues("", "", "200", "", ""))) + assert.Equal(t, float64(1), testutil.ToFloat64(m.RequestsTotal.WithLabelValues("", "", "400", "", ""))) + assert.Equal(t, float64(1), testutil.ToFloat64(m.RequestsTotal.WithLabelValues("", "", "500", "", ""))) +} + +func TestMetricsMiddleware_RequestDuration(t *testing.T) { + gin.SetMode(gin.TestMode) + reg := newTestRegistry() + m := NewMetrics(reg) + + r := gin.New() + r.Use(MetricsMiddleware(m)) + r.GET("/slow", func(c *gin.Context) { + time.Sleep(100 * time.Millisecond) + c.Status(200) + }) + + req := httptest.NewRequest("GET", "/slow", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, 1, testutil.CollectAndCount(m.RequestDuration)) +} diff --git a/common/metrics/middleware.go b/common/metrics/middleware.go new file mode 100644 index 0000000..33d5e28 --- /dev/null +++ b/common/metrics/middleware.go @@ -0,0 +1,28 @@ +package metrics + +import ( + "strconv" + "time" + + "github.com/gin-gonic/gin" +) + +// MetricsMiddleware 是一个 Gin 中间件,采集活跃请求数、请求延迟和状态码。 +func MetricsMiddleware(m *Metrics) gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + + m.ActiveRequests.WithLabelValues("").Inc() + defer func() { + m.ActiveRequests.WithLabelValues("").Dec() + + elapsed := time.Since(start).Seconds() + status := strconv.Itoa(c.Writer.Status()) + + m.RequestsTotal.WithLabelValues("", "", status, "", "").Inc() + m.RequestDuration.WithLabelValues("", "", "", "").Observe(elapsed) + }() + + c.Next() + } +}