From 5b81e6e23f8da9b9ecbb0725e58723f73a27dee3 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 29 Sep 2023 17:18:59 +0200 Subject: [PATCH] Add metrics collector for HTTP status codes --- app/api/api.go | 5 ++++ http/middleware/log/log.go | 8 ++++++- http/server.go | 34 ++++++++++++++++++++++---- http/server/server.go | 8 +++++++ monitor/http.go | 49 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 http/server/server.go create mode 100644 monitor/http.go diff --git a/app/api/api.go b/app/api/api.go index d250afe5..16ce3e0e 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -1558,6 +1558,9 @@ func (a *api) start(ctx context.Context) error { a.sidecarserver.Handler = autocertManager.HTTPChallengeHandler(sidecarserverhandler) } + metrics.Register(monitor.NewHTTPCollector("HTTPS", mainserverhandler)) + metrics.Register(monitor.NewHTTPCollector("HTTP", sidecarserverhandler)) + wgStart.Add(1) a.wgStop.Add(1) @@ -1583,6 +1586,8 @@ func (a *api) start(ctx context.Context) error { sendError(err) }() + } else { + metrics.Register(monitor.NewHTTPCollector("HTTP", mainserverhandler)) } if a.rtmpserver != nil { diff --git a/http/middleware/log/log.go b/http/middleware/log/log.go index 83e30b0e..6f84fd6a 100644 --- a/http/middleware/log/log.go +++ b/http/middleware/log/log.go @@ -16,11 +16,13 @@ type Config struct { // Skipper defines a function to skip middleware. Skipper middleware.Skipper Logger log.Logger + Status func(code int) } var DefaultConfig = Config{ Skipper: middleware.DefaultSkipper, - Logger: log.New("HTTP"), + Logger: log.New(""), + Status: nil, } func New() echo.MiddlewareFunc { @@ -76,6 +78,10 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { latency := time.Since(start) + if config.Status != nil { + config.Status(res.Status) + } + if raw != "" { path = path + "?" + raw } diff --git a/http/server.go b/http/server.go index a1155ea4..daa75e64 100644 --- a/http/server.go +++ b/http/server.go @@ -32,6 +32,7 @@ import ( "fmt" "net/http" "strings" + "sync" "github.com/datarhei/core/v16/cluster" cfgstore "github.com/datarhei/core/v16/config/store" @@ -43,6 +44,7 @@ import ( api "github.com/datarhei/core/v16/http/handler/api" httplog "github.com/datarhei/core/v16/http/log" "github.com/datarhei/core/v16/http/router" + serverhandler "github.com/datarhei/core/v16/http/server" "github.com/datarhei/core/v16/http/validator" "github.com/datarhei/core/v16/iam" "github.com/datarhei/core/v16/log" @@ -104,10 +106,6 @@ type CorsConfig struct { Origins []string } -type Server interface { - ServeHTTP(w http.ResponseWriter, r *http.Request) -} - type server struct { logger log.Logger @@ -154,6 +152,11 @@ type server struct { profiling bool readOnly bool + + metrics struct { + lock sync.Mutex + status map[int]uint64 + } } type filesystem struct { @@ -164,7 +167,7 @@ type filesystem struct { middleware echo.MiddlewareFunc } -func NewServer(config Config) (Server, error) { +func NewServer(config Config) (serverhandler.Server, error) { s := &server{ logger: config.Logger, mimeTypesFile: config.MimeTypesFile, @@ -172,6 +175,8 @@ func NewServer(config Config) (Server, error) { readOnly: config.ReadOnly, } + s.metrics.status = map[int]uint64{} + s.filesystems = map[string]*filesystem{} corsPrefixes := map[string][]string{ @@ -327,6 +332,12 @@ func NewServer(config Config) (Server, error) { s.middleware.log = mwlog.NewWithConfig(mwlog.Config{ Logger: s.logger, + Status: func(code int) { + s.metrics.lock.Lock() + defer s.metrics.lock.Unlock() + + s.metrics.status[code]++ + }, }) s.v3handler.widget = api.NewWidget(api.WidgetConfig{ @@ -458,6 +469,19 @@ 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{} + + s.metrics.lock.Lock() + defer s.metrics.lock.Unlock() + + for code, value := range s.metrics.status { + status[code] = value + } + + return status +} + func (s *server) setRoutes() { gzipMiddleware := mwgzip.NewWithConfig(mwgzip.Config{ Level: mwgzip.BestSpeed, diff --git a/http/server/server.go b/http/server/server.go new file mode 100644 index 00000000..893bf462 --- /dev/null +++ b/http/server/server.go @@ -0,0 +1,8 @@ +package server + +import "net/http" + +type Server interface { + ServeHTTP(w http.ResponseWriter, r *http.Request) + HTTPStatus() map[int]uint64 +} diff --git a/monitor/http.go b/monitor/http.go new file mode 100644 index 00000000..cd3d82b6 --- /dev/null +++ b/monitor/http.go @@ -0,0 +1,49 @@ +package monitor + +import ( + "strconv" + + "github.com/datarhei/core/v16/http/server" + "github.com/datarhei/core/v16/monitor/metric" +) + +type httpCollector struct { + handler server.Server + name string + statusDescr *metric.Description +} + +func NewHTTPCollector(name string, handler server.Server) metric.Collector { + c := &httpCollector{ + handler: handler, + name: name, + } + + c.statusDescr = metric.NewDesc("http_status", "Total return status", []string{"name", "code"}) + + return c +} + +func (c *httpCollector) Prefix() string { + return "filesystem" +} + +func (c *httpCollector) Describe() []*metric.Description { + return []*metric.Description{ + c.statusDescr, + } +} + +func (c *httpCollector) Collect() metric.Metrics { + status := c.handler.HTTPStatus() + + metrics := metric.NewMetrics() + + for code, count := range status { + metrics.Add(metric.NewValue(c.statusDescr, float64(count), c.name, strconv.Itoa(code))) + } + + return metrics +} + +func (c *httpCollector) Stop() {}