github.com/letsencrypt/boulder@v0.20251208.0/metrics/measured_http/http.go (about)

     1  package measured_http
     2  
     3  import (
     4  	"net/http"
     5  	"strconv"
     6  
     7  	"github.com/jmhodges/clock"
     8  	"github.com/prometheus/client_golang/prometheus"
     9  	"github.com/prometheus/client_golang/prometheus/promauto"
    10  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    11  )
    12  
    13  // responseWriterWithStatus satisfies http.ResponseWriter, but keeps track of the
    14  // status code for gathering stats.
    15  type responseWriterWithStatus struct {
    16  	http.ResponseWriter
    17  	code int
    18  }
    19  
    20  // WriteHeader stores a status code for generating stats.
    21  func (r *responseWriterWithStatus) WriteHeader(code int) {
    22  	r.code = code
    23  	r.ResponseWriter.WriteHeader(code)
    24  }
    25  
    26  // Write writes the body and sets the status code to 200 if a status code
    27  // has not already been set.
    28  func (r *responseWriterWithStatus) Write(body []byte) (int, error) {
    29  	if r.code == 0 {
    30  		r.code = http.StatusOK
    31  	}
    32  	return r.ResponseWriter.Write(body)
    33  }
    34  
    35  // serveMux is a partial interface wrapper for the one method http.ServeMux
    36  // exposes that we use. This prevents us from accidentally developing an
    37  // overly-specific reliance on that concrete type.
    38  type serveMux interface {
    39  	Handler(*http.Request) (http.Handler, string)
    40  }
    41  
    42  // MeasuredHandler wraps an http.Handler and records prometheus stats
    43  type MeasuredHandler struct {
    44  	serveMux
    45  	clk clock.Clock
    46  	// Normally this is always responseTime, but we override it for testing.
    47  	stat *prometheus.HistogramVec
    48  	// inFlightRequestsGauge is a gauge that tracks the number of requests
    49  	// currently in flight, labeled by endpoint.
    50  	inFlightRequestsGauge *prometheus.GaugeVec
    51  }
    52  
    53  func New(m serveMux, clk clock.Clock, stats prometheus.Registerer, opts ...otelhttp.Option) http.Handler {
    54  	responseTime := promauto.With(stats).NewHistogramVec(prometheus.HistogramOpts{
    55  		Name: "response_time",
    56  		Help: "Time taken to respond to a request",
    57  	}, []string{"endpoint", "method", "code"})
    58  
    59  	inFlightRequestsGauge := promauto.With(stats).NewGaugeVec(prometheus.GaugeOpts{
    60  		Name: "in_flight_requests",
    61  		Help: "Tracks the number of WFE requests currently in flight, labeled by endpoint.",
    62  	}, []string{"endpoint"})
    63  
    64  	return otelhttp.NewHandler(&MeasuredHandler{
    65  		serveMux:              m,
    66  		clk:                   clk,
    67  		stat:                  responseTime,
    68  		inFlightRequestsGauge: inFlightRequestsGauge,
    69  	}, "server", opts...)
    70  }
    71  
    72  func (h *MeasuredHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    73  	begin := h.clk.Now()
    74  	rwws := &responseWriterWithStatus{w, 0}
    75  
    76  	subHandler, pattern := h.Handler(r)
    77  	h.inFlightRequestsGauge.WithLabelValues(pattern).Inc()
    78  	defer h.inFlightRequestsGauge.WithLabelValues(pattern).Dec()
    79  
    80  	// Use the method string only if it's a recognized HTTP method. This avoids
    81  	// ballooning timeseries with invalid methods from public input.
    82  	var method string
    83  	switch r.Method {
    84  	case http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut,
    85  		http.MethodPatch, http.MethodDelete, http.MethodConnect,
    86  		http.MethodOptions, http.MethodTrace:
    87  		method = r.Method
    88  	default:
    89  		method = "unknown"
    90  	}
    91  
    92  	defer func() {
    93  		h.stat.With(prometheus.Labels{
    94  			"endpoint": pattern,
    95  			"method":   method,
    96  			"code":     strconv.Itoa(rwws.code),
    97  		}).Observe(h.clk.Since(begin).Seconds())
    98  	}()
    99  
   100  	subHandler.ServeHTTP(rwws, r)
   101  }