From cf956501ac79ef556dd3d4fa9bcd2fca9469b705 Mon Sep 17 00:00:00 2001 From: Daan Sprenkels Date: Sun, 8 Dec 2019 21:56:02 +0100 Subject: [PATCH] metrics: Add http_requests metric --- go.sum | 1 + handlers.go | 8 ++++---- metrics.go | 59 +++++++++++++++++++++++++++++++---------------------- router.go | 33 ++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/go.sum b/go.sum index 420f99d..97e2ffd 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/handlers.go b/handlers.go index 128b3b2..66ff5e9 100644 --- a/handlers.go +++ b/handlers.go @@ -50,9 +50,9 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request) if badID { renderError(w, r, http.StatusNotFound, "malformed file id") return - } else { - panic(err) } + // unexpected error + panic(err) } filePath := rl.fs.FilePath(fu.ID, fu.FileName) @@ -62,9 +62,9 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request) log.Printf("error: %v should exist according to the database, but it doesn't", filePath) renderError(w, r, http.StatusNotFound, "file not found") return - } else { - panic(err) } + // unexpected error + panic(err) } w.Header().Set("Content-Type", fu.ContentType) io.Copy(w, file) diff --git a/metrics.go b/metrics.go index 0fda66c..35d3e4f 100644 --- a/metrics.go +++ b/metrics.go @@ -10,34 +10,45 @@ import ( "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" bolt "go.etcd.io/bbolt" ) -func StartMetricsServer(addr string, db *db.Database) { - var ( - _ = promauto.NewGaugeFunc(prometheus.GaugeOpts{ - Namespace: "rushlink", - Subsystem: "pastes", - Name: "urls_total", - Help: "The current amount of pastes in the database.", - }, func() float64 { - var metric float64 - if err := db.Bolt.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte("pastes")) - if bucket == nil { - return errors.New("bucket 'pastes' could not be found") - } - metric = float64(bucket.Stats().KeyN) - return nil - }); err != nil { - log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric")) - return 0 - } - return metric - }) - ) +const metricNamespace = "rushlink" + +var metricRequestsTotalCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: "http", + Name: "requests_total", + Help: "How many HTTP requests processed, partitioned by status code and HTTP method.", +}, []string{"code", "method"}) + +func metricURLsTotal(database *db.Database) float64 { + var metric float64 + if err := database.Bolt.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte("pastes")) + if bucket == nil { + return errors.New("bucket 'pastes' could not be found") + } + metric = float64(bucket.Stats().KeyN) + return nil + }); err != nil { + log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric")) + return 0 + } + return metric +} + +// StartMetricsServer starts sering Prometheus metrics exports on addr +func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) { + prometheus.MustRegister(metricRequestsTotalCounter) + + prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{ + Namespace: metricNamespace, + Subsystem: "pastes", + Name: "urls_total", + Help: "The current amount of pastes in the database.", + }, func() float64 { return metricURLsTotal(database) })) router := mux.NewRouter() router.Handle("/metrics", promhttp.Handler()).Methods("GET") diff --git a/router.go b/router.go index cb2c2da..6d3caaa 100644 --- a/router.go +++ b/router.go @@ -5,6 +5,7 @@ import ( "log" "net/http" "runtime/debug" + "strconv" "time" "gitea.hashru.nl/dsprenkels/rushlink/internal/db" @@ -37,6 +38,37 @@ func recoveryMiddleware(next http.Handler) http.Handler { }) } +type statusResponseWriter struct { + Inner http.ResponseWriter + StatusCode int +} + +func (w *statusResponseWriter) Header() http.Header { + return w.Inner.Header() +} + +func (w *statusResponseWriter) Write(buf []byte) (int, error) { + if w.StatusCode == 0 { + w.WriteHeader(http.StatusOK) + } + return w.Inner.Write(buf) +} + +func (w *statusResponseWriter) WriteHeader(statusCode int) { + w.StatusCode = statusCode + w.Inner.WriteHeader(statusCode) +} + +func metricsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srw := statusResponseWriter{Inner: w} + next.ServeHTTP(&srw, r) + status := strconv.Itoa(srw.StatusCode) + metricRequestsTotalCounter.WithLabelValues(status, r.Method).Inc() + }) +} + +// StartMainServer starts the main http server listening on addr. func StartMainServer(addr string, db *db.Database, fs *db.FileStore) { rl := rushlink{ db: db, @@ -46,6 +78,7 @@ func StartMainServer(addr string, db *db.Database, fs *db.FileStore) { // Initialize Gorilla router router := mux.NewRouter() router.Use(recoveryMiddleware) + router.Use(metricsMiddleware) router.HandleFunc("/", rl.indexGetHandler).Methods("GET") router.HandleFunc("/", rl.newPasteHandler).Methods("POST") router.HandleFunc("/{key:[A-Za-z0-9-_]{4,}}", rl.viewPasteHandler).Methods("GET")