diff --git a/internal/infrastructure/middleware/metrics.go b/internal/infrastructure/middleware/metrics.go index 60024615..14bbd1c9 100644 --- a/internal/infrastructure/middleware/metrics.go +++ b/internal/infrastructure/middleware/metrics.go @@ -2,11 +2,14 @@ package middleware import ( "errors" + "log/slog" "strconv" + "strings" "time" "github.com/labstack/echo/v4" echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/podengo-project/idmsvc-backend/internal/infrastructure/context" "github.com/podengo-project/idmsvc-backend/internal/metrics" "github.com/prometheus/client_golang/prometheus" ) @@ -40,6 +43,8 @@ func MetricsMiddlewareWithConfig(config *MetricsConfig) echo.MiddlewareFunc { err := next(ctx) + logger := context.LogFromCtx(ctx.Request().Context()) + method := ctx.Request().Method path := MatchedRoute(ctx) status := ctx.Response().Status @@ -50,6 +55,25 @@ func MetricsMiddlewareWithConfig(config *MetricsConfig) echo.MiddlewareFunc { status = httpErr.Code } statusStr := strconv.Itoa(status) + headerBuf := strings.Builder{} + headerSize := 0.0 + if errHeaderBuf := ctx.Request().Header.Write(&headerBuf); errHeaderBuf == nil { + headerSize = float64(len(headerBuf.String())) + config.Metrics.HTTPRequestHeaderSize.WithLabelValues(statusStr, method, path).Observe(headerSize) + } else { + logger.Warn("writing headers in string buffer", + slog.String("err", errHeaderBuf.Error()), + ) + } + bodySize := float64(ctx.Request().ContentLength) + config.Metrics.HTTPRequestBodySize.WithLabelValues(statusStr, method, path).Observe(bodySize) + logger.Debug("measured request size", + slog.String("status", statusStr), + slog.String("method", method), + slog.String("path", path), + slog.Float64("header_size", headerSize), + slog.Float64("body_size", bodySize), + ) config.Metrics.HTTPRequestDuration.WithLabelValues(statusStr, method, path).Observe(time.Since(start).Seconds()) diff --git a/internal/infrastructure/middleware/metrics_test.go b/internal/infrastructure/middleware/metrics_test.go index 4e808ce6..65858d78 100644 --- a/internal/infrastructure/middleware/metrics_test.go +++ b/internal/infrastructure/middleware/metrics_test.go @@ -69,6 +69,7 @@ func TestMetricsMiddlewareWithConfigCreation(t *testing.T) { } e := echo.New() + e.Use(ContextLogConfig(&LogConfig{})) m := MetricsMiddlewareWithConfig(config) e.Use(m) path := "/api/idmsvc/v1/domains" diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index e1ef3eaf..8b9c0166 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -13,8 +13,12 @@ const ( // Metrics holds all the Prometheus metrics for the application type Metrics struct { - // HTTPRequestDuration is a histogram that measures the duration of HTTP requests + // HTTPRequestDuration is a histogram that measures the duration of the HTTP requests HTTPRequestDuration *prometheus.HistogramVec + // HTTPRequestHeaderSize is a histogram that measures the size of the HTTP request headers. + HTTPRequestHeaderSize *prometheus.HistogramVec + // HTTPRequestBodySize is a histogram that measures the size of the HTTP request bodys. + HTTPRequestBodySize *prometheus.HistogramVec reg *prometheus.Registry } @@ -37,6 +41,20 @@ func NewMetrics(reg *prometheus.Registry) *Metrics { Help: "Duration of HTTP requests", Buckets: prometheus.ExponentialBuckets(0.0005, 2, 20), }, []string{"status", "method", "path"}), + HTTPRequestHeaderSize: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: NameSpace, + Name: "http_request_header_size", + Help: "Size of the HTTP request headers", + // Bucket limited to 32KB + Buckets: []float64{1024, 2 * 1024, 4 * 1024, 8 * 1024, 16 * 1024, 32 * 1024}, + }, []string{"status", "method", "path"}), + HTTPRequestBodySize: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: NameSpace, + Name: "http_request_body_size", + Help: "Size of the HTTP request bodies", + // Bucket limited to 128KB + Buckets: []float64{1024, 2 * 1024, 4 * 1024, 8 * 1024, 16 * 1024, 32 * 1024, 64 * 1024, 128 * 1024}, + }, []string{"status", "method", "path"}), } reg.MustRegister(collectors.NewBuildInfoCollector())