X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/d27d94532c173335cae9dddc30f1cc9a2e372bd7..09963d2b633769549eea8739be3b541835839462:/sdk/go/httpserver/metrics.go diff --git a/sdk/go/httpserver/metrics.go b/sdk/go/httpserver/metrics.go index 1f7d44752b..f3291f6c5e 100644 --- a/sdk/go/httpserver/metrics.go +++ b/sdk/go/httpserver/metrics.go @@ -10,15 +10,21 @@ import ( "strings" "time" - "git.curoverse.com/arvados.git/sdk/go/stats" - "github.com/Sirupsen/logrus" + "git.arvados.org/arvados.git/sdk/go/auth" + "git.arvados.org/arvados.git/sdk/go/stats" "github.com/gogo/protobuf/jsonpb" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/sirupsen/logrus" ) type Handler interface { http.Handler + + // Returns an http.Handler that serves the Handler's metrics + // data at /metrics and /metrics.json, and passes other + // requests through to next. + ServeAPI(token string, next http.Handler) http.Handler } type metrics struct { @@ -34,6 +40,8 @@ func (*metrics) Levels() []logrus.Level { return logrus.AllLevels } +// Fire implements logrus.Hook in order to collect data points from +// request logs. func (m *metrics) Fire(ent *logrus.Entry) error { if tts, ok := ent.Data["timeToStatus"].(stats.Duration); !ok { } else if method, ok := ent.Data["reqMethod"].(string); !ok { @@ -57,23 +65,57 @@ func (m *metrics) exportJSON(w http.ResponseWriter, req *http.Request) { w.Write([]byte{']'}) } +// ServeHTTP implements http.Handler. func (m *metrics) ServeHTTP(w http.ResponseWriter, req *http.Request) { - switch { - case req.Method != "GET" && req.Method != "HEAD": - m.next.ServeHTTP(w, req) - case req.URL.Path == "/metrics.json": - m.exportJSON(w, req) - case req.URL.Path == "/metrics": - m.exportProm.ServeHTTP(w, req) - default: - m.next.ServeHTTP(w, req) - } + m.next.ServeHTTP(w, req) +} + +// ServeAPI returns a new http.Handler that serves current data at +// metrics API endpoints (currently "GET /metrics(.json)?") and passes +// other requests through to next. +// +// If the given token is not empty, that token must be supplied by a +// client in order to access the metrics endpoints. +// +// Typical example: +// +// m := Instrument(...) +// srv := http.Server{Handler: m.ServeAPI("secrettoken", m)} +func (m *metrics) ServeAPI(token string, next http.Handler) http.Handler { + jsonMetrics := auth.RequireLiteralToken(token, http.HandlerFunc(m.exportJSON)) + plainMetrics := auth.RequireLiteralToken(token, m.exportProm) + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + switch { + case req.Method != "GET" && req.Method != "HEAD": + next.ServeHTTP(w, req) + case req.URL.Path == "/metrics.json": + jsonMetrics.ServeHTTP(w, req) + case req.URL.Path == "/metrics": + plainMetrics.ServeHTTP(w, req) + default: + next.ServeHTTP(w, req) + } + }) } -func Instrument(logger *logrus.Logger, next http.Handler) Handler { +// Instrument returns a new Handler that passes requests through to +// the next handler in the stack, and tracks metrics of those +// requests. +// +// For the metrics to be accurate, the caller must ensure every +// request passed to the Handler also passes through +// LogRequests(...), and vice versa. +// +// If registry is nil, a new registry is created. +// +// If logger is nil, logrus.StandardLogger() is used. +func Instrument(registry *prometheus.Registry, logger *logrus.Logger, next http.Handler) Handler { if logger == nil { logger = logrus.StandardLogger() } + if registry == nil { + registry = prometheus.NewRegistry() + } reqDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Name: "request_duration_seconds", Help: "Summary of request duration.", @@ -82,7 +124,6 @@ func Instrument(logger *logrus.Logger, next http.Handler) Handler { Name: "time_to_status_seconds", Help: "Summary of request TTFB.", }, []string{"code", "method"}) - registry := prometheus.NewRegistry() registry.MustRegister(timeToStatus) registry.MustRegister(reqDuration) m := &metrics{