Browse Source

create a gin middleware to collect request metrics and export them to the Prometheus route

feature/txsel-l1frozenqueue-acccreation
Pantani 3 years ago
parent
commit
9245247ee4
3 changed files with 90 additions and 1 deletions
  1. +7
    -0
      api/api.go
  2. +5
    -1
      api/handlers.go
  3. +78
    -0
      metric/request.go

+ 7
- 0
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

+ 5
- 1
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(),
})

+ 78
- 0
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()
}
}

Loading…
Cancel
Save