Merge branch '18631-shell-login-sync'
[arvados.git] / sdk / go / httpserver / metrics.go
index 1f7d44752bea34bb3cbf39092cb9c5b6d2707b54..f3291f6c5ec8a373770f12b3ebae64ba08617b34 100644 (file)
@@ -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{