From 9245247ee41864943d8d3168bf5337fb49ed4c19 Mon Sep 17 00:00:00 2001 From: Pantani Date: Thu, 25 Mar 2021 09:31:29 -0300 Subject: [PATCH] create a gin middleware to collect request metrics and export them to the Prometheus route --- api/api.go | 7 +++++ api/handlers.go | 6 +++- metric/request.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 metric/request.go diff --git a/api/api.go b/api/api.go index 926bc86..89f9661 100644 --- a/api/api.go +++ b/api/api.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/l2db" + "github.com/hermeznetwork/hermez-node/metric" "github.com/hermeznetwork/tracerr" ) @@ -50,6 +51,12 @@ func NewAPI( hermezAddress: consts.HermezAddress, } + middleware, err := metric.PrometheusMiddleware() + if err != nil { + return nil, err + } + server.Use(middleware) + v1 := server.Group("/v1") // Add coordinator endpoints diff --git a/api/handlers.go b/api/handlers.go index 6e9a2e9..aeefd04 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -8,6 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/log" + "github.com/hermeznetwork/hermez-node/metric" "github.com/hermeznetwork/tracerr" "github.com/lib/pq" "github.com/russross/meddler" @@ -46,7 +47,9 @@ var ( func retSQLErr(err error, c *gin.Context) { log.Warnw("HTTP API SQL request error", "err", err) - errMsg := tracerr.Unwrap(err).Error() + unwrapErr := tracerr.Unwrap(err) + metric.CollectError(unwrapErr) + errMsg := unwrapErr.Error() retDupKey := func(errCode pq.ErrorCode) { // https://www.postgresql.org/docs/current/errcodes-appendix.html if errCode == "23505" { @@ -80,6 +83,7 @@ func retSQLErr(err error, c *gin.Context) { func retBadReq(err error, c *gin.Context) { log.Warnw("HTTP API Bad request error", "err", err) + metric.CollectError(err) c.JSON(http.StatusBadRequest, errorMsg{ Message: err.Error(), }) diff --git a/metric/request.go b/metric/request.go new file mode 100644 index 0000000..b9dcf8d --- /dev/null +++ b/metric/request.go @@ -0,0 +1,78 @@ +package metric + +import ( + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" +) + +const ( + favicon = "/favicon.ico" +) + +// Prometheus contains the metrics gathered by the instance and its path +type Prometheus struct { + reqCnt *prometheus.CounterVec + reqDur *prometheus.HistogramVec +} + +// NewPrometheus generates a new set of metrics with a certain subsystem name +func NewPrometheus() (*Prometheus, error) { + reqCnt := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: namespaceAPI, + Name: "requests_total", + Help: "How many HTTP requests processed, partitioned by status code and HTTP method", + }, + []string{"code", "method", "path"}, + ) + if err := registerCollector(reqCnt); err != nil { + return nil, err + } + reqDur := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: namespaceAPI, + Name: "request_duration_seconds", + Help: "The HTTP request latencies in seconds", + }, + []string{"code", "method", "path"}, + ) + if err := registerCollector(reqDur); err != nil { + return nil, err + } + return &Prometheus{ + reqCnt: reqCnt, + reqDur: reqDur, + }, nil +} + +// PrometheusMiddleware creates the prometheus collector and +// defines status handler function for the middleware +func PrometheusMiddleware() (gin.HandlerFunc, error) { + p, err := NewPrometheus() + if err != nil { + return nil, err + } + return p.Middleware(), nil +} + +// Middleware defines status handler function for middleware +func (p *Prometheus) Middleware() gin.HandlerFunc { + return func(c *gin.Context) { + if c.Request.URL.Path == favicon { + c.Next() + return + } + start := time.Now() + c.Next() + + status := strconv.Itoa(c.Writer.Status()) + elapsed := float64(time.Since(start)) / float64(time.Second) + fullPath := c.FullPath() + + p.reqDur.WithLabelValues(status, c.Request.Method, fullPath).Observe(elapsed) + p.reqCnt.WithLabelValues(status, c.Request.Method, fullPath).Inc() + } +}