Label request metrics with standard HTTP methods only (closes #274)
check / check (push) Waiting to run
check / check (push) Waiting to run
The metrics middleware labelled each request with its method as sent, and Go's HTTP server accepts any method name that is a valid token, so each made-up method a client sent added series that were never removed. The method label is now the request's method only for the nine standard methods, and "other" for any other. The middleware hands the metrics library a copy of the request carrying the label and serves the next handler the original, so handlers still see the method the client sent. Model: opus-5-5
This commit was merged in pull request #277.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -314,6 +315,58 @@ func requestCountsByHandler(
|
||||
return counts
|
||||
}
|
||||
|
||||
// TestMetricsGivesMadeUpMethodsOneLabel checks that the metrics middleware
|
||||
// gives requests with methods that are not standard ones a single method
|
||||
// label, "other", and still serves each request with the method it was sent
|
||||
// with. With a label per method, any client could add series to the
|
||||
// registry without limit by making up a new method for each request.
|
||||
func TestMetricsGivesMadeUpMethodsOneLabel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// The line /metrics shows once both made-up methods have been served.
|
||||
const want = `http_request_duration_seconds_count{` +
|
||||
`code="200",handler="unmatched",method="other",service=""} 2`
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
mw := &Middleware{
|
||||
log: slog.Default(),
|
||||
config: &config.Config{},
|
||||
metricsRegistry: registry,
|
||||
}
|
||||
|
||||
var served []string
|
||||
|
||||
handler := mw.Metrics()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
served = append(served, r.Method)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
methods := []string{"FROBNICATE", "WIBBLE"}
|
||||
for _, method := range methods {
|
||||
handler.ServeHTTP(httptest.NewRecorder(), httptest.NewRequestWithContext(
|
||||
t.Context(), method, "/", nil))
|
||||
}
|
||||
|
||||
t.Logf("methods the handler was served: %v", served)
|
||||
|
||||
if !slices.Equal(served, methods) {
|
||||
t.Errorf("methods the handler was served = %v, want %v",
|
||||
served, methods)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(rec,
|
||||
httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, "/metrics", nil))
|
||||
|
||||
if !strings.Contains(rec.Body.String(), want) {
|
||||
t.Errorf("/metrics does not count both made-up methods as one; "+
|
||||
"want the line %q in:\n%s", want, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoggingLeavesOutSubmittedSigningKey checks that a login, a POST /
|
||||
// whose form carries the signing key, leaves no trace of the key in the
|
||||
// request's log line.
|
||||
|
||||
Reference in New Issue
Block a user