Deploy main eacf1e7 to prod
check / check (push) Waiting to run

Replaces #559, which conflicted.

Model: opus-5-5
This commit is contained in:
2026-10-25 01:55:36 +00:00
parent 1647b43aa6
commit 537374e4cc
311 changed files with 35614 additions and 14120 deletions
+77 -19
View File
@@ -7,9 +7,7 @@ import (
"github.com/go-chi/chi"
httpmetrics "github.com/slok/go-http-metrics/metrics"
prommetrics "github.com/slok/go-http-metrics/metrics/prometheus"
ghmm "github.com/slok/go-http-metrics/middleware"
"github.com/slok/go-http-metrics/middleware/std"
)
// inflightHandler is the fixed `handler` label on
@@ -40,10 +38,11 @@ const unmatchedMethod = unmatchedRoute
//
// The pattern is what bounds the label's domain to the routes the
// service registers. The path does not bound it at all — every byte
// after /webhook/ is client-chosen, so labelling by path lets any
// after /h/ is client-chosen, so labelling by path lets any
// unauthenticated client mint permanent series at will, and publishes
// the entrypoint UUID (the receiver's only credential) in the scrape
// while doing it.
// while doing it. The access log and the 429 line read it for the
// same reason: see ReceiverRoute.
//
// chi populates the route context during routeHTTP, so this is only
// valid once routing has run. Every caller below is on the recording
@@ -151,17 +150,17 @@ func (r boundedLabelRecorder) AddInflightRequests(
var _ httpmetrics.Recorder = boundedLabelRecorder{}
// Metrics returns middleware that records Prometheus HTTP metrics on
// the default registry, which is the one the /metrics route gathers.
// Metrics returns middleware that records Prometheus HTTP metrics
// with the Middleware's one recorder, which New builds on the registry
// it is given: in the application, the one the /metrics route serves.
// Every call reuses that recorder, so any number of routers can
// install it.
func (s *Middleware) Metrics() func(http.Handler) http.Handler {
return metricsMiddleware(
prommetrics.NewRecorder(prommetrics.Config{}),
)
return metricsMiddleware(s.metricsRecorder)
}
// metricsMiddleware builds the recording middleware against a given
// recorder, so tests can gather from a registry of their own instead
// of the process-wide default.
// recorder, so tests can gather from a registry of their own.
func metricsMiddleware(
rec httpmetrics.Recorder,
) func(http.Handler) http.Handler {
@@ -170,13 +169,72 @@ func metricsMiddleware(
})
return func(next http.Handler) http.Handler {
// The handler id is unmatchedRoute rather than "" so that
// the client-chosen URL path never enters the metrics
// pipeline at all: an empty id is the library's signal to
// substitute it. boundedLabelRecorder overwrites this value
// on every observation, so it is reachable only if that
// decorator is removed — in which case the metrics collapse
// to one series instead of leaking again.
return std.Handler(unmatchedRoute, mdlw, next)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mw := &metricsResponseWriter{
ResponseWriter: w,
request: r,
statusCode: http.StatusOK,
}
// The handler id is unmatchedRoute rather than "" so
// that the client-chosen URL path never enters the
// metrics pipeline at all: an empty id is the library's
// signal to substitute it. boundedLabelRecorder
// overwrites this value on every observation, so it is
// reachable only if that decorator is removed — in which
// case the metrics collapse to one series instead of
// leaking again.
mdlw.Measure(unmatchedRoute, mw, func() {
next.ServeHTTP(mw, r)
})
})
}
}
// metricsResponseWriter records the status code and body size of a
// response, and hands them with the request to go-http-metrics'
// Measure as its Reporter.
//
// It stands in for the library's std.Handler, whose writer has no
// Unwrap: behind it, http.ResponseController cannot reach net/http's
// own writer, so a handler's write deadline fails with metrics on.
type metricsResponseWriter struct {
http.ResponseWriter
request *http.Request
statusCode int
bytesWritten int64
}
func (w *metricsResponseWriter) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
func (w *metricsResponseWriter) Write(b []byte) (int, error) {
w.bytesWritten += int64(len(b))
//nolint:wrapcheck // Pass the writer's own error through unchanged.
return w.ResponseWriter.Write(b)
}
// Unwrap lets http.ResponseController reach the writer underneath, so
// a handler can still flush or set a write deadline with metrics on.
func (w *metricsResponseWriter) Unwrap() http.ResponseWriter {
return w.ResponseWriter
}
func (w *metricsResponseWriter) Method() string { return w.request.Method }
func (w *metricsResponseWriter) Context() context.Context {
return w.request.Context()
}
func (w *metricsResponseWriter) URLPath() string { return w.request.URL.Path }
func (w *metricsResponseWriter) StatusCode() int { return w.statusCode }
func (w *metricsResponseWriter) BytesWritten() int64 { return w.bytesWritten }
var _ ghmm.Reporter = (*metricsResponseWriter)(nil)