Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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{"*"}}),
Expand All @@ -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),
Expand All @@ -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)
}
11 changes: 11 additions & 0 deletions internal/api/metrics.go
Original file line number Diff line number Diff line change
@@ -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())
}
76 changes: 76 additions & 0 deletions internal/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -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
}