From bde3d57fdd1a1916099187877d2fe22e24f3b6f2 Mon Sep 17 00:00:00 2001 From: Martin Saporiti Date: Fri, 5 Apr 2024 12:36:22 -0300 Subject: [PATCH] chore: add prometheus metrics --- cmd/main.go | 13 ++++++- internal/api/metrics.go | 11 ++++++ internal/metrics/metrics.go | 76 +++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 internal/api/metrics.go create mode 100644 internal/metrics/metrics.go diff --git a/cmd/main.go b/cmd/main.go index 6430866..7bac7d7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -11,14 +11,18 @@ import ( chiMiddleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "github.com/iden3/go-iden3-auth/v2/loaders" + "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" "github.com/0xPolygonID/verifier-backend/internal/api" "github.com/0xPolygonID/verifier-backend/internal/config" "github.com/0xPolygonID/verifier-backend/internal/errors" + "github.com/0xPolygonID/verifier-backend/internal/metrics" ) func main() { + registerMetrics() + cfg, err := config.Load() if err != nil { log.WithField("error", err).Error("cannot load config") @@ -28,8 +32,8 @@ func main() { keysLoader := &loaders.FSKeyLoader{Dir: cfg.KeyDIR} mux := chi.NewRouter() - mux.Use( + metrics.PrometheusTotalRequestsMiddleware, chiMiddleware.RequestID, chiMiddleware.Recoverer, cors.Handler(cors.Options{AllowedOrigins: []string{"*"}}), @@ -40,6 +44,7 @@ func main() { api.HandlerFromMux(api.NewStrictHandlerWithOptions(apiServer, nil, api.StrictHTTPServerOptions{RequestErrorHandlerFunc: errors.RequestErrorHandlerFunc}), mux) api.RegisterStatic(mux) + api.RegisterMetrics(mux) server := &http.Server{ Addr: fmt.Sprintf(":%s", cfg.ApiPort), @@ -58,3 +63,9 @@ func main() { <-quit log.Info("Shutting down") } + +func registerMetrics() { + prometheus.MustRegister(metrics.TotalRequests) + prometheus.MustRegister(metrics.HttpDuration) + prometheus.MustRegister(metrics.ResponseStatus) +} diff --git a/internal/api/metrics.go b/internal/api/metrics.go new file mode 100644 index 0000000..c2ad6f1 --- /dev/null +++ b/internal/api/metrics.go @@ -0,0 +1,11 @@ +package api + +import ( + "github.com/go-chi/chi/v5" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// RegisterMetrics - registers the static routes for the API. +func RegisterMetrics(mux *chi.Mux) { + mux.Handle("/metrics", promhttp.Handler()) +} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 0000000..d2379da --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,76 @@ +package metrics + +import ( + "net/http" + "strconv" + + "github.com/prometheus/client_golang/prometheus" +) + +// TotalRequests - CounterVec to store the total number of requests per path +var TotalRequests = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total_per_path", + Help: "Number of get requests per path", + }, + []string{"path"}, +) + +// ResponseStatus - CounterVec to store the status of the response +var ResponseStatus = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_response_status", + Help: "Status of HTTP response", + }, + []string{"status"}, +) + +// HttpDuration - HistogramVec to store the duration of the HTTP requests per path +var HttpDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "http_response_time_seconds_per_path", + Help: "Duration of HTTP requests per path", +}, []string{"path"}) + +type responseWriter struct { + http.ResponseWriter + statusCode int +} + +func newResponseWriter(w http.ResponseWriter) *responseWriter { + return &responseWriter{w, http.StatusOK} +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.statusCode = code + rw.ResponseWriter.WriteHeader(code) +} + +// PrometheusTotalRequestsMiddleware - Middleware to record metrics for the API +func PrometheusTotalRequestsMiddleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + path := r.URL.Path + if mustBeExcluded(path) { + next.ServeHTTP(w, r.WithContext(ctx)) + return + } + timer := prometheus.NewTimer(HttpDuration.WithLabelValues(path)) + rw := newResponseWriter(w) + next.ServeHTTP(rw, r) + + statusCode := rw.statusCode + ResponseStatus.WithLabelValues(strconv.Itoa(statusCode)).Inc() + TotalRequests.WithLabelValues(path).Inc() + + timer.ObserveDuration() + } + return http.HandlerFunc(fn) +} + +func mustBeExcluded(path string) bool { + mustBe := false + if path == "" || path == "/static/docs/api/api.yaml" || path == "/metrics" { + mustBe = true + } + return mustBe +}