Use pattern to determine endpoint metrics. (#2689)
This ensures we don't create infinite metrics based on users hitting non-existent endpoints.
This commit is contained in:
parent
eccca3ccd4
commit
d59188c676
|
|
@ -235,14 +235,16 @@ as generated by Boulder's single-ocsp command.
|
||||||
}
|
}
|
||||||
|
|
||||||
func mux(scope metrics.Scope, responderPath string, source cfocsp.Source) http.Handler {
|
func mux(scope metrics.Scope, responderPath string, source cfocsp.Source) http.Handler {
|
||||||
m := http.StripPrefix(responderPath, cfocsp.NewResponder(source))
|
stripPrefix := http.StripPrefix(responderPath, cfocsp.NewResponder(source))
|
||||||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "GET" && r.URL.Path == "/" {
|
if r.Method == "GET" && r.URL.Path == "/" {
|
||||||
w.Header().Set("Cache-Control", "max-age=43200") // Cache for 12 hours
|
w.Header().Set("Cache-Control", "max-age=43200") // Cache for 12 hours
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.ServeHTTP(w, r)
|
stripPrefix.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
return measured_http.New(h, clock.Default())
|
m := http.NewServeMux()
|
||||||
|
m.Handle("/", h)
|
||||||
|
return measured_http.New(m, clock.Default())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ package measured_http
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jmhodges/clock"
|
"github.com/jmhodges/clock"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
@ -38,56 +36,32 @@ func (r *responseWriterWithStatus) WriteHeader(code int) {
|
||||||
|
|
||||||
// MeasuredHandler wraps an http.Handler and records prometheus stats
|
// MeasuredHandler wraps an http.Handler and records prometheus stats
|
||||||
type MeasuredHandler struct {
|
type MeasuredHandler struct {
|
||||||
http.Handler
|
*http.ServeMux
|
||||||
clk clock.Clock
|
clk clock.Clock
|
||||||
// Normally this is always responseTime, but we override it for testing.
|
// Normally this is always responseTime, but we override it for testing.
|
||||||
stat *prometheus.HistogramVec
|
stat *prometheus.HistogramVec
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(h http.Handler, clk clock.Clock) *MeasuredHandler {
|
func New(m *http.ServeMux, clk clock.Clock) *MeasuredHandler {
|
||||||
return &MeasuredHandler{
|
return &MeasuredHandler{
|
||||||
Handler: h,
|
ServeMux: m,
|
||||||
clk: clk,
|
clk: clk,
|
||||||
stat: responseTime,
|
stat: responseTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var endpointShortNameRegexp = regexp.MustCompile("^[a-z-]*$")
|
|
||||||
|
|
||||||
// endpointFromPath turns a request path into a value suitable for a Prometheus
|
|
||||||
// label value, by eliminating path components that vary widely (like user ids
|
|
||||||
// and authorization ids). It uses a simple heuristic: Remove everything after
|
|
||||||
// the first path component that doesn't match lowercase a-z, plus hyphen. This
|
|
||||||
// happens to work very well with Boulder's WFE.
|
|
||||||
func endpointFromPath(path string) string {
|
|
||||||
// Remove any query
|
|
||||||
path = strings.Split(path, "?")[0]
|
|
||||||
components := strings.Split(path, "/")
|
|
||||||
var i int
|
|
||||||
var v string
|
|
||||||
for i, v = range components {
|
|
||||||
matched := endpointShortNameRegexp.MatchString(v)
|
|
||||||
if !matched {
|
|
||||||
return strings.Join(components[:i], "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MeasuredHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *MeasuredHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
begin := h.clk.Now()
|
begin := h.clk.Now()
|
||||||
rwws := &responseWriterWithStatus{w, 0}
|
rwws := &responseWriterWithStatus{w, 0}
|
||||||
// Copy in case handlers down the chain use StripPrefix, which modifies
|
|
||||||
// URL path.
|
|
||||||
endpoint := endpointFromPath(r.URL.Path)
|
|
||||||
|
|
||||||
|
subHandler, pattern := h.Handler(r)
|
||||||
defer func() {
|
defer func() {
|
||||||
h.stat.With(prometheus.Labels{
|
h.stat.With(prometheus.Labels{
|
||||||
"endpoint": endpoint,
|
"endpoint": pattern,
|
||||||
"method": r.Method,
|
"method": r.Method,
|
||||||
"code": fmt.Sprintf("%d", rwws.code),
|
"code": fmt.Sprintf("%d", rwws.code),
|
||||||
}).Observe(h.clk.Since(begin).Seconds())
|
}).Observe(h.clk.Since(begin).Seconds())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
h.Handler.ServeHTTP(rwws, r)
|
subHandler.ServeHTTP(rwws, r)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,27 +12,6 @@ import (
|
||||||
io_prometheus_client "github.com/prometheus/client_model/go"
|
io_prometheus_client "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEndpointFromPath(t *testing.T) {
|
|
||||||
tc := []struct {
|
|
||||||
input, expected string
|
|
||||||
}{
|
|
||||||
{"/", "/"},
|
|
||||||
{"/acme", "/acme"},
|
|
||||||
{"/acme/new-authz", "/acme/new-authz"},
|
|
||||||
{"/acme/new-authz/", "/acme/new-authz/"},
|
|
||||||
{"/acme/authz/1234", "/acme/authz"},
|
|
||||||
{"/acme/authz/aGVsbG8K/1234", "/acme/authz"},
|
|
||||||
{"/directory?foo=bar", "/directory"},
|
|
||||||
}
|
|
||||||
for _, c := range tc {
|
|
||||||
output := endpointFromPath(c.input)
|
|
||||||
if output != c.expected {
|
|
||||||
t.Errorf("endpointFromPath(%q) = %q (expected %q)",
|
|
||||||
c.input, output, c.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type sleepyHandler struct {
|
type sleepyHandler struct {
|
||||||
clk clock.FakeClock
|
clk clock.FakeClock
|
||||||
}
|
}
|
||||||
|
|
@ -54,8 +33,10 @@ func TestMeasuring(t *testing.T) {
|
||||||
},
|
},
|
||||||
[]string{"endpoint", "method", "code"})
|
[]string{"endpoint", "method", "code"})
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/foo", sleepyHandler{clk})
|
||||||
mh := MeasuredHandler{
|
mh := MeasuredHandler{
|
||||||
Handler: sleepyHandler{clk},
|
ServeMux: mux,
|
||||||
clk: clk,
|
clk: clk,
|
||||||
stat: stat,
|
stat: stat,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue