diff --git a/admin.go b/admin.go index e37caf83..bd927f0f 100644 --- a/admin.go +++ b/admin.go @@ -43,7 +43,6 @@ import ( "github.com/caddyserver/caddy/v2/notify" "github.com/caddyserver/certmagic" - "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -209,53 +208,26 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi muxWrap.enforceOrigin = admin.EnforceOrigin } - addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) { - labels := prometheus.Labels{"path": pattern, "handler": handlerLabel} - h = instrumentHandlerCounter( - adminMetrics.requestCount.MustCurryWith(labels), - h, - ) - muxWrap.mux.Handle(pattern, h) - } - // addRoute just calls muxWrap.mux.Handle after - // wrapping the handler with error handling - addRoute := func(pattern string, handlerLabel string, h AdminHandler) { - wrapper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - err := h.ServeHTTP(w, r) - if err != nil { - labels := prometheus.Labels{ - "path": pattern, - "handler": handlerLabel, - "method": strings.ToUpper(r.Method), - } - adminMetrics.requestErrors.With(labels).Inc() - } - muxWrap.handleError(w, r, err) - }) - addRouteWithMetrics(pattern, handlerLabel, wrapper) - } - const handlerLabel = "admin" - // register standard config control endpoints - addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig)) - addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID)) - addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop)) + muxWrap.Handle("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig)) + muxWrap.Handle("/id/", handlerLabel, AdminHandlerFunc(handleConfigID)) + muxWrap.Handle("/stop", handlerLabel, AdminHandlerFunc(handleStop)) // register debugging endpoints - addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index)) - addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline)) - addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile)) - addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol)) - addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace)) - addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler()) + muxWrap.HandleStd("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index)) + muxWrap.HandleStd("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline)) + muxWrap.HandleStd("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile)) + muxWrap.HandleStd("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol)) + muxWrap.HandleStd("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace)) + muxWrap.HandleStd("/debug/vars", handlerLabel, expvar.Handler()) // register third-party module endpoints for _, m := range GetModules("admin.api") { router := m.New().(AdminRouter) handlerLabel := m.ID.Name() for _, route := range router.Routes() { - addRoute(route.Pattern, handlerLabel, route.Handler) + muxWrap.Handle(route.Pattern, handlerLabel, route.Handler) } admin.routers = append(admin.routers, router) } @@ -712,6 +684,21 @@ type adminHandler struct { remoteControl *RemoteAdmin } +// Handle registers an AdminHandler-type handler for the given pattern. +func (h adminHandler) Handle(pattern, label string, handler AdminHandler) { + h.mux.Handle(pattern, instrumentAdminHandler(pattern, label, handler, h.handleError)) +} + +// HandleStd registers an http.Handler-type handler for the given pattern. +func (h adminHandler) HandleStd(pattern, label string, handler http.Handler) { + h.Handle(pattern, label, AdminHandlerFunc(func( + w http.ResponseWriter, r *http.Request, + ) error { + handler.ServeHTTP(w, r) + return nil + })) +} + // ServeHTTP is the external entry point for API requests. // It will only be called once per request. func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -1294,7 +1281,7 @@ var ( // will get deleted before the process gracefully exits. func PIDFile(filename string) error { pid := []byte(strconv.Itoa(os.Getpid()) + "\n") - err := os.WriteFile(filename, pid, 0600) + err := os.WriteFile(filename, pid, 0o600) if err != nil { return err } diff --git a/metrics.go b/metrics.go index 325006fb..bca7f121 100644 --- a/metrics.go +++ b/metrics.go @@ -3,7 +3,7 @@ package caddy import ( "net/http" - "github.com/caddyserver/caddy/v2/internal/metrics" + internal "github.com/caddyserver/caddy/v2/internal/metrics" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promauto" @@ -15,13 +15,13 @@ func init() { const ns, sub = "caddy", "admin" - adminMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{ + adminMetrics.requests = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Subsystem: sub, Name: "http_requests_total", Help: "Counter of requests made to the Admin API's HTTP endpoints.", }, []string{"handler", "path", "code", "method"}) - adminMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + adminMetrics.errors = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Subsystem: sub, Name: "http_request_errors_total", @@ -31,29 +31,33 @@ func init() { // adminMetrics is a collection of metrics that can be tracked for the admin API. var adminMetrics = struct { - requestCount *prometheus.CounterVec - requestErrors *prometheus.CounterVec + requests *prometheus.CounterVec + errors *prometheus.CounterVec }{} -// Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names -// instead of lower-casing them. -// -// Unlike promhttp.InstrumentHandlerCounter, this assumes a "code" and "method" -// label is present, and will panic otherwise. -func instrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - d := newDelegator(w) - next.ServeHTTP(d, r) - counter.With(prometheus.Labels{ - "code": metrics.SanitizeCode(d.status), - "method": metrics.SanitizeMethod(r.Method), - }).Inc() - }) -} +// instrumentAdminHandler wraps the handler with total and errored-out request count +// in a manner similar to promhttp.InstrumentHandlerCounter. All errors are handled +// using the passed error handler. +func instrumentAdminHandler(pattern, handlerLabel string, + h AdminHandler, errorHandler func(http.ResponseWriter, *http.Request, error), +) http.HandlerFunc { + labels := prometheus.Labels{"path": pattern, "handler": handlerLabel} + requests := adminMetrics.requests.MustCurryWith(labels) + errors := adminMetrics.errors.MustCurryWith(labels) -func newDelegator(w http.ResponseWriter) *delegator { - return &delegator{ - ResponseWriter: w, + return func(w http.ResponseWriter, r *http.Request) { + d := delegator{ResponseWriter: w} + labels := prometheus.Labels{ + "method": internal.SanitizeMethod(r.Method), + } + + if err := h.ServeHTTP(w, r); err != nil { + errors.With(labels).Inc() + errorHandler(w, r, err) + } + + labels["code"] = internal.SanitizeCode(d.status) + requests.With(labels).Inc() } }