package metrics import ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" ) // Copyright 2022 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package metrics is adapted from // https://github.com/bwplotka/correlator/tree/main/examples/observability/ping/pkg/httpinstrumentation // https://github.com/prometheus/client_golang/blob/main/examples/middleware/httpmiddleware/httpmiddleware.go type Middleware interface { // WrapHandler wraps the given HTTP handler for instrumentation. WrapHandler(handlerName string, handler http.Handler) http.HandlerFunc } type middleware struct { buckets []float64 registry prometheus.Registerer } // WrapHandler wraps the given HTTP handler for instrumentation: // It registers four metric collectors (if not already done) and reports HTTP // metrics to the (newly or already) registered collectors. // Each has a constant label named "handler" with the provided handlerName as // value. func (m *middleware) WrapHandler(handlerName string, handler http.Handler) http.HandlerFunc { reg := prometheus.WrapRegistererWith(prometheus.Labels{"handler": handlerName}, m.registry) requestsTotal := promauto.With(reg).NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Tracks the number of HTTP requests.", }, []string{"method", "code", "host"}, ) requestDuration := promauto.With(reg).NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "Tracks the latencies for HTTP requests.", Buckets: m.buckets, }, []string{"method", "code", "host"}, ) requestSize := promauto.With(reg).NewSummaryVec( prometheus.SummaryOpts{ Name: "http_request_size_bytes", Help: "Tracks the size of HTTP requests.", }, []string{"method", "code", "host"}, ) responseSize := promauto.With(reg).NewSummaryVec( prometheus.SummaryOpts{ Name: "http_response_size_bytes", Help: "Tracks the size of HTTP responses.", }, []string{"method", "code", "host"}, ) hostCtxGetter := promhttp.WithLabelFromCtx("host", func(ctx context.Context) string { s, _ := ctx.Value(hostCtxKey(0)).(string) return s }) // Wraps the provided http.Handler to observe the request result with the provided metrics. base := promhttp.InstrumentHandlerCounter( requestsTotal, promhttp.InstrumentHandlerDuration( requestDuration, promhttp.InstrumentHandlerRequestSize( requestSize, promhttp.InstrumentHandlerResponseSize( responseSize, handler, hostCtxGetter, ), hostCtxGetter, ), hostCtxGetter, ), hostCtxGetter, ) return base.ServeHTTP } // New returns a Middleware interface. func New(registry prometheus.Registerer, buckets []float64) Middleware { if buckets == nil { buckets = prometheus.ExponentialBuckets(0.1, 1.5, 5) } return &middleware{ buckets: buckets, registry: registry, } } type hostCtxKey uint8 func AddHostCtx(req *http.Request) *http.Request { return req.WithContext(context.WithValue(req.Context(), hostCtxKey(0), req.Host)) }