From 91874e6cafd82a25e96bc0f502fadf6dd2667196 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 10 Oct 2024 12:18:22 +0200 Subject: [PATCH] Extend http status metrics by method and path --- http/middleware/log/log.go | 34 ++++++++++++++++++++++------------ http/server.go | 20 +++++++++++--------- http/server/server.go | 2 +- monitor/http.go | 9 +++++---- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/http/middleware/log/log.go b/http/middleware/log/log.go index 2a225c27..3a78a6c9 100644 --- a/http/middleware/log/log.go +++ b/http/middleware/log/log.go @@ -16,7 +16,7 @@ type Config struct { // Skipper defines a function to skip middleware. Skipper middleware.Skipper Logger log.Logger - Status func(code int) + Status func(code int, method, path string, size int64, ttfb time.Duration) } var DefaultConfig = Config{ @@ -76,10 +76,15 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { res.Writer = writer req.Body = reader + if w.ttfb.IsZero() { + w.ttfb = start + } + latency := time.Since(start) + ttfb := time.Since(w.ttfb) if config.Status != nil { - config.Status(res.Status) + config.Status(res.Status, req.Method, c.Path(), w.size, ttfb) } if raw != "" { @@ -87,16 +92,17 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } logger := config.Logger.WithFields(log.Fields{ - "client": c.RealIP(), - "method": req.Method, - "path": path, - "proto": req.Proto, - "status": res.Status, - "status_text": http.StatusText(res.Status), - "tx_size_bytes": w.size, - "rx_size_bytes": r.size, - "latency_ms": latency.Milliseconds(), - "user_agent": req.Header.Get("User-Agent"), + "client": c.RealIP(), + "method": req.Method, + "path": path, + "proto": req.Proto, + "status": res.Status, + "status_text": http.StatusText(res.Status), + "tx_size_bytes": w.size, + "rx_size_bytes": r.size, + "latency_ms": latency.Milliseconds(), + "latency_ttfb_ms": ttfb.Milliseconds(), + "user_agent": req.Header.Get("User-Agent"), }) logger.Debug().Log("") @@ -110,12 +116,16 @@ type sizeWriter struct { http.ResponseWriter size int64 + ttfb time.Time } func (w *sizeWriter) Write(body []byte) (int, error) { n, err := w.ResponseWriter.Write(body) w.size += int64(n) + if w.ttfb.IsZero() { + w.ttfb = time.Now() + } return n, err } diff --git a/http/server.go b/http/server.go index 09322e87..c7dc68ab 100644 --- a/http/server.go +++ b/http/server.go @@ -30,9 +30,11 @@ package http import ( "fmt" + "maps" "net/http" "strings" "sync" + "time" "github.com/datarhei/core/v16/cluster" cfgstore "github.com/datarhei/core/v16/config/store" @@ -166,7 +168,7 @@ type server struct { metrics struct { lock sync.Mutex - status map[int]uint64 + status map[string]uint64 } } @@ -186,7 +188,7 @@ func NewServer(config Config) (serverhandler.Server, error) { readOnly: config.ReadOnly, } - s.metrics.status = map[int]uint64{} + s.metrics.status = map[string]uint64{} s.filesystems = map[string]*filesystem{} @@ -344,11 +346,13 @@ func NewServer(config Config) (serverhandler.Server, error) { s.middleware.log = mwlog.NewWithConfig(mwlog.Config{ Logger: s.logger, - Status: func(code int) { + Status: func(code int, method, path string, size int64, ttfb time.Duration) { + key := fmt.Sprintf("%d:%s:%s", code, method, path) + s.metrics.lock.Lock() defer s.metrics.lock.Unlock() - s.metrics.status[code]++ + s.metrics.status[key]++ }, }) @@ -483,15 +487,13 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } -func (s *server) HTTPStatus() map[int]uint64 { - status := map[int]uint64{} +func (s *server) HTTPStatus() map[string]uint64 { + status := map[string]uint64{} s.metrics.lock.Lock() defer s.metrics.lock.Unlock() - for code, value := range s.metrics.status { - status[code] = value - } + maps.Copy(status, s.metrics.status) return status } diff --git a/http/server/server.go b/http/server/server.go index 893bf462..d46ea92e 100644 --- a/http/server/server.go +++ b/http/server/server.go @@ -4,5 +4,5 @@ import "net/http" type Server interface { ServeHTTP(w http.ResponseWriter, r *http.Request) - HTTPStatus() map[int]uint64 + HTTPStatus() map[string]uint64 } diff --git a/monitor/http.go b/monitor/http.go index cd3d82b6..2330d4d1 100644 --- a/monitor/http.go +++ b/monitor/http.go @@ -1,7 +1,7 @@ package monitor import ( - "strconv" + "strings" "github.com/datarhei/core/v16/http/server" "github.com/datarhei/core/v16/monitor/metric" @@ -19,7 +19,7 @@ func NewHTTPCollector(name string, handler server.Server) metric.Collector { name: name, } - c.statusDescr = metric.NewDesc("http_status", "Total return status", []string{"name", "code"}) + c.statusDescr = metric.NewDesc("http_status", "Total return status count", []string{"name", "code", "method", "path"}) return c } @@ -39,8 +39,9 @@ func (c *httpCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - for code, count := range status { - metrics.Add(metric.NewValue(c.statusDescr, float64(count), c.name, strconv.Itoa(code))) + for key, count := range status { + vals := strings.SplitN(key, ":", 3) + metrics.Add(metric.NewValue(c.statusDescr, float64(count), c.name, vals[0], vals[1], vals[2])) } return metrics