Label request metrics with standard HTTP methods only (closes #274) #277
@@ -685,7 +685,9 @@ See `configs/config.example.yml` for all options with defaults.
|
|||||||
`metrics.username` and `metrics.password` are set. A request's `handler` label
|
`metrics.username` and `metrics.password` are set. A request's `handler` label
|
||||||
is the pattern of the route it matches, such as `/v1/image/*`, never its path.
|
is the pattern of the route it matches, such as `/v1/image/*`, never its path.
|
||||||
Requests that match no route share the label `unmatched`: those the router
|
Requests that match no route share the label `unmatched`: those the router
|
||||||
answers 404 or 405, and CORS preflights
|
answers 404 or 405, and CORS preflights. A request's `method` label is its
|
||||||
|
method when that is `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`,
|
||||||
|
`OPTIONS`, `CONNECT` or `TRACE`, and `other` for any other method
|
||||||
- **Logging**: stdlib slog
|
- **Logging**: stdlib slog
|
||||||
|
|
||||||
## Entrypoints
|
## Entrypoints
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ P2: security: per-origin rate limiting
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-15 the request metrics record any method that is not a standard one as
|
||||||
|
`other` (closes #274): their `method` label was each request's method as sent,
|
||||||
|
and Go's HTTP server accepts any method name, so a client sending made-up
|
||||||
|
methods added series to the Prometheus registry that were never removed. The
|
||||||
|
label is now the request's method only for `GET`, `HEAD`, `POST`, `PUT`,
|
||||||
|
`PATCH`, `DELETE`, `OPTIONS`, `CONNECT` and `TRACE`. The metrics library reads
|
||||||
|
the method from the request it is handed, so the metrics middleware hands it a
|
||||||
|
copy of the request carrying the label; the handlers still see the method the
|
||||||
|
client sent.
|
||||||
- 2026-10-15 logging out ends every login session (closes #256): a login session
|
- 2026-10-15 logging out ends every login session (closes #256): a login session
|
||||||
was a cookie with nothing kept on the server, so `/logout` removed it from one
|
was a cookie with nothing kept on the server, so `/logout` removed it from one
|
||||||
browser and a copy of it kept working for its 30 days. Every login is made
|
browser and a copy of it kept working for its 30 days. Every login is made
|
||||||
|
|||||||
@@ -220,7 +220,10 @@ func (s *Middleware) CORS() func(http.Handler) http.Handler {
|
|||||||
// pattern of the chi route a request matches, such as /v1/image/*, or
|
// pattern of the chi route a request matches, such as /v1/image/*, or
|
||||||
// "unmatched" for a request that matches none, so the routes fix how many
|
// "unmatched" for a request that matches none, so the routes fix how many
|
||||||
// values the label has. It is never the path, with which any client could
|
// values the label has. It is never the path, with which any client could
|
||||||
// add series to the registry without limit.
|
// add series to the registry without limit. Its method label is the
|
||||||
|
// request's method when that is a standard one, such as GET, and "other"
|
||||||
|
// for any other: Go's HTTP server accepts any method name, so a label per
|
||||||
|
// method would let a client add series the same way.
|
||||||
func (s *Middleware) Metrics() func(http.Handler) http.Handler {
|
func (s *Middleware) Metrics() func(http.Handler) http.Handler {
|
||||||
mdlw := ghmm.New(ghmm.Config{
|
mdlw := ghmm.New(ghmm.Config{
|
||||||
Recorder: metrics.NewRecorder(metrics.Config{
|
Recorder: metrics.NewRecorder(metrics.Config{
|
||||||
@@ -230,7 +233,19 @@ func (s *Middleware) Metrics() func(http.Handler) http.Handler {
|
|||||||
|
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
std.Handler(routePattern(r), mdlw, next).ServeHTTP(w, r)
|
// The metrics library takes the method label from the request
|
||||||
|
// it is handed, so it is handed a shallow copy of r whose
|
||||||
|
// method is the label. next is served r itself, through the
|
||||||
|
// response writer the library wraps to see the status and size.
|
||||||
|
labelled := r.WithContext(r.Context())
|
||||||
|
labelled.Method = methodLabel(r.Method)
|
||||||
|
|
||||||
|
serveNext := http.HandlerFunc(
|
||||||
|
func(wrapped http.ResponseWriter, _ *http.Request) {
|
||||||
|
next.ServeHTTP(wrapped, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
std.Handler(routePattern(r), mdlw, serveNext).ServeHTTP(w, labelled)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,6 +290,20 @@ func routePattern(r *http.Request) string {
|
|||||||
return pattern
|
return pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// methodLabel returns method when it is one of the standard methods net/http
|
||||||
|
// names: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, CONNECT and TRACE.
|
||||||
|
// For any other method it returns "other".
|
||||||
|
func methodLabel(method string) string {
|
||||||
|
switch method {
|
||||||
|
case http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut,
|
||||||
|
http.MethodPatch, http.MethodDelete, http.MethodOptions,
|
||||||
|
http.MethodConnect, http.MethodTrace:
|
||||||
|
return method
|
||||||
|
default:
|
||||||
|
return "other"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MetricsAuth returns a basic auth middleware for the metrics endpoint.
|
// MetricsAuth returns a basic auth middleware for the metrics endpoint.
|
||||||
func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler {
|
func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler {
|
||||||
return basicauth.New(
|
return basicauth.New(
|
||||||
|
|||||||
Reference in New Issue
Block a user