metrics: Add http_requests metric

This commit is contained in:
Daan Sprenkels 2019-12-08 21:56:02 +01:00
parent 0cfad96b68
commit cf956501ac
4 changed files with 73 additions and 28 deletions

1
go.sum
View File

@ -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.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 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= 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-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 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=

View File

@ -50,9 +50,9 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request)
if badID { if badID {
renderError(w, r, http.StatusNotFound, "malformed file id") renderError(w, r, http.StatusNotFound, "malformed file id")
return return
} else {
panic(err)
} }
// unexpected error
panic(err)
} }
filePath := rl.fs.FilePath(fu.ID, fu.FileName) 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) log.Printf("error: %v should exist according to the database, but it doesn't", filePath)
renderError(w, r, http.StatusNotFound, "file not found") renderError(w, r, http.StatusNotFound, "file not found")
return return
} else {
panic(err)
} }
// unexpected error
panic(err)
} }
w.Header().Set("Content-Type", fu.ContentType) w.Header().Set("Content-Type", fu.ContentType)
io.Copy(w, file) io.Copy(w, file)

View File

@ -10,34 +10,45 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
func StartMetricsServer(addr string, db *db.Database) { const metricNamespace = "rushlink"
var (
_ = promauto.NewGaugeFunc(prometheus.GaugeOpts{ var metricRequestsTotalCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "rushlink", Namespace: metricNamespace,
Subsystem: "pastes", Subsystem: "http",
Name: "urls_total", Name: "requests_total",
Help: "The current amount of pastes in the database.", Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
}, func() float64 { }, []string{"code", "method"})
var metric float64
if err := db.Bolt.View(func(tx *bolt.Tx) error { func metricURLsTotal(database *db.Database) float64 {
bucket := tx.Bucket([]byte("pastes")) var metric float64
if bucket == nil { if err := database.Bolt.View(func(tx *bolt.Tx) error {
return errors.New("bucket 'pastes' could not be found") bucket := tx.Bucket([]byte("pastes"))
} if bucket == nil {
metric = float64(bucket.Stats().KeyN) return errors.New("bucket 'pastes' could not be found")
return nil }
}); err != nil { metric = float64(bucket.Stats().KeyN)
log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric")) return nil
return 0 }); err != nil {
} log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
return 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 := mux.NewRouter()
router.Handle("/metrics", promhttp.Handler()).Methods("GET") router.Handle("/metrics", promhttp.Handler()).Methods("GET")

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
"strconv"
"time" "time"
"gitea.hashru.nl/dsprenkels/rushlink/internal/db" "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) { func StartMainServer(addr string, db *db.Database, fs *db.FileStore) {
rl := rushlink{ rl := rushlink{
db: db, db: db,
@ -46,6 +78,7 @@ func StartMainServer(addr string, db *db.Database, fs *db.FileStore) {
// Initialize Gorilla router // Initialize Gorilla router
router := mux.NewRouter() router := mux.NewRouter()
router.Use(recoveryMiddleware) router.Use(recoveryMiddleware)
router.Use(metricsMiddleware)
router.HandleFunc("/", rl.indexGetHandler).Methods("GET") router.HandleFunc("/", rl.indexGetHandler).Methods("GET")
router.HandleFunc("/", rl.newPasteHandler).Methods("POST") router.HandleFunc("/", rl.newPasteHandler).Methods("POST")
router.HandleFunc("/{key:[A-Za-z0-9-_]{4,}}", rl.viewPasteHandler).Methods("GET") router.HandleFunc("/{key:[A-Za-z0-9-_]{4,}}", rl.viewPasteHandler).Methods("GET")