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 {
|
||||
m := http.StripPrefix(responderPath, cfocsp.NewResponder(source))
|
||||
stripPrefix := http.StripPrefix(responderPath, cfocsp.NewResponder(source))
|
||||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && r.URL.Path == "/" {
|
||||
w.Header().Set("Cache-Control", "max-age=43200") // Cache for 12 hours
|
||||
w.WriteHeader(200)
|
||||
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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jmhodges/clock"
|
||||
"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
|
||||
type MeasuredHandler struct {
|
||||
http.Handler
|
||||
*http.ServeMux
|
||||
clk clock.Clock
|
||||
// Normally this is always responseTime, but we override it for testing.
|
||||
stat *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
func New(h http.Handler, clk clock.Clock) *MeasuredHandler {
|
||||
func New(m *http.ServeMux, clk clock.Clock) *MeasuredHandler {
|
||||
return &MeasuredHandler{
|
||||
Handler: h,
|
||||
clk: clk,
|
||||
stat: responseTime,
|
||||
ServeMux: m,
|
||||
clk: clk,
|
||||
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) {
|
||||
begin := h.clk.Now()
|
||||
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() {
|
||||
h.stat.With(prometheus.Labels{
|
||||
"endpoint": endpoint,
|
||||
"endpoint": pattern,
|
||||
"method": r.Method,
|
||||
"code": fmt.Sprintf("%d", rwws.code),
|
||||
}).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"
|
||||
)
|
||||
|
||||
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 {
|
||||
clk clock.FakeClock
|
||||
}
|
||||
|
@ -54,10 +33,12 @@ func TestMeasuring(t *testing.T) {
|
|||
},
|
||||
[]string{"endpoint", "method", "code"})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/foo", sleepyHandler{clk})
|
||||
mh := MeasuredHandler{
|
||||
Handler: sleepyHandler{clk},
|
||||
clk: clk,
|
||||
stat: stat,
|
||||
ServeMux: mux,
|
||||
clk: clk,
|
||||
stat: stat,
|
||||
}
|
||||
mh.ServeHTTP(httptest.NewRecorder(), &http.Request{
|
||||
URL: &url.URL{Path: "/foo"},
|
||||
|
|
Loading…
Reference in New Issue