Skip to content

Commit b7e53bc

Browse files
committed
Prometheus middleware should not call c.Error() as it causes double error logging.
Handle status code properly when next returns an error
1 parent 063b8fd commit b7e53bc

File tree

2 files changed

+56
-13
lines changed

2 files changed

+56
-13
lines changed

prometheus/prometheus.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package prometheus
2222

2323
import (
2424
"bytes"
25+
"errors"
2526
"net/http"
2627
"os"
2728
"strconv"
@@ -364,7 +365,7 @@ func (p *Prometheus) Use(e *echo.Echo) {
364365

365366
// HandlerFunc defines handler function for middleware
366367
func (p *Prometheus) HandlerFunc(next echo.HandlerFunc) echo.HandlerFunc {
367-
return func(c echo.Context) (err error) {
368+
return func(c echo.Context) error {
368369
if c.Path() == p.MetricsPath {
369370
return next(c)
370371
}
@@ -375,18 +376,22 @@ func (p *Prometheus) HandlerFunc(next echo.HandlerFunc) echo.HandlerFunc {
375376
start := time.Now()
376377
reqSz := computeApproximateRequestSize(c.Request())
377378

378-
if err = next(c); err != nil {
379-
c.Error(err)
380-
}
379+
err := next(c)
381380

382-
status := strconv.Itoa(c.Response().Status)
383-
url := p.RequestCounterURLLabelMappingFunc(c)
381+
status := c.Response().Status
382+
if err != nil {
383+
var httpError *echo.HTTPError
384+
if errors.As(err, &httpError) {
385+
status = httpError.Code
386+
}
387+
if status == 0 || status == http.StatusOK {
388+
status = http.StatusInternalServerError
389+
}
390+
}
384391

385392
elapsed := float64(time.Since(start)) / float64(time.Second)
386-
resSz := float64(c.Response().Size)
387-
388-
p.reqDur.WithLabelValues(status, c.Request().Method, url).Observe(elapsed)
389393

394+
url := p.RequestCounterURLLabelMappingFunc(c)
390395
if len(p.URLLabelFromContext) > 0 {
391396
u := c.Get(p.URLLabelFromContext)
392397
if u == nil {
@@ -395,11 +400,15 @@ func (p *Prometheus) HandlerFunc(next echo.HandlerFunc) echo.HandlerFunc {
395400
url = u.(string)
396401
}
397402

398-
p.reqCnt.WithLabelValues(status, c.Request().Method, p.RequestCounterHostLabelMappingFunc(c), url).Inc()
399-
p.reqSz.WithLabelValues(status, c.Request().Method, url).Observe(float64(reqSz))
400-
p.resSz.WithLabelValues(status, c.Request().Method, url).Observe(resSz)
403+
statusStr := strconv.Itoa(status)
404+
p.reqDur.WithLabelValues(statusStr, c.Request().Method, url).Observe(elapsed)
405+
p.reqCnt.WithLabelValues(statusStr, c.Request().Method, p.RequestCounterHostLabelMappingFunc(c), url).Inc()
406+
p.reqSz.WithLabelValues(statusStr, c.Request().Method, url).Observe(float64(reqSz))
407+
408+
resSz := float64(c.Response().Size)
409+
p.resSz.WithLabelValues(statusStr, c.Request().Method, url).Observe(resSz)
401410

402-
return
411+
return err
403412
}
404413
}
405414

prometheus/prometheus_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,37 @@ func TestMetricsPushGateway(t *testing.T) {
134134
})
135135
unregister(p)
136136
}
137+
138+
func TestMetricsForErrors(t *testing.T) {
139+
e := echo.New()
140+
p := NewPrometheus("echo", nil)
141+
p.Use(e)
142+
143+
e.GET("/handler_for_ok", func(c echo.Context) error {
144+
return c.JSON(http.StatusOK, "OK")
145+
})
146+
e.GET("/handler_for_nok", func(c echo.Context) error {
147+
return c.JSON(http.StatusConflict, "NOK")
148+
})
149+
e.GET("/handler_for_error", func(c echo.Context) error {
150+
return echo.NewHTTPError(http.StatusBadGateway, "BAD")
151+
})
152+
153+
g := gofight.New()
154+
g.GET("/handler_for_ok").Run(e, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) })
155+
156+
g.GET("/handler_for_nok").Run(e, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusConflict, r.Code) })
157+
g.GET("/handler_for_nok").Run(e, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusConflict, r.Code) })
158+
159+
g.GET("/handler_for_error").Run(e, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusBadGateway, r.Code) })
160+
161+
g.GET(p.MetricsPath).Run(e, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
162+
assert.Equal(t, http.StatusOK, r.Code)
163+
body := r.Body.String()
164+
assert.Contains(t, body, fmt.Sprintf("%s_requests_total", p.Subsystem))
165+
assert.Contains(t, body, `echo_requests_total{code="200",host="",method="GET",url="/handler_for_ok"} 1`)
166+
assert.Contains(t, body, `echo_requests_total{code="409",host="",method="GET",url="/handler_for_nok"} 2`)
167+
assert.Contains(t, body, `echo_requests_total{code="502",host="",method="GET",url="/handler_for_error"} 1`)
168+
})
169+
unregister(p)
170+
}

0 commit comments

Comments
 (0)