refactor: metrics integration with caddy/admin.go

This commit is contained in:
renbou 2022-07-08 00:59:53 +08:00
parent f259ed52bb
commit 755c22af13
No known key found for this signature in database
GPG key ID: C2781315C81C98DE
2 changed files with 53 additions and 62 deletions

View file

@ -43,7 +43,6 @@ import (
"github.com/caddyserver/caddy/v2/notify" "github.com/caddyserver/caddy/v2/notify"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
@ -209,53 +208,26 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi
muxWrap.enforceOrigin = admin.EnforceOrigin 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" const handlerLabel = "admin"
// register standard config control endpoints // register standard config control endpoints
addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig)) muxWrap.Handle("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID)) muxWrap.Handle("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop)) muxWrap.Handle("/stop", handlerLabel, AdminHandlerFunc(handleStop))
// register debugging endpoints // register debugging endpoints
addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index)) muxWrap.HandleStd("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline)) muxWrap.HandleStd("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline))
addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile)) muxWrap.HandleStd("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile))
addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol)) muxWrap.HandleStd("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol))
addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace)) muxWrap.HandleStd("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace))
addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler()) muxWrap.HandleStd("/debug/vars", handlerLabel, expvar.Handler())
// register third-party module endpoints // register third-party module endpoints
for _, m := range GetModules("admin.api") { for _, m := range GetModules("admin.api") {
router := m.New().(AdminRouter) router := m.New().(AdminRouter)
handlerLabel := m.ID.Name() handlerLabel := m.ID.Name()
for _, route := range router.Routes() { for _, route := range router.Routes() {
addRoute(route.Pattern, handlerLabel, route.Handler) muxWrap.Handle(route.Pattern, handlerLabel, route.Handler)
} }
admin.routers = append(admin.routers, router) admin.routers = append(admin.routers, router)
} }
@ -712,6 +684,21 @@ type adminHandler struct {
remoteControl *RemoteAdmin 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. // ServeHTTP is the external entry point for API requests.
// It will only be called once per request. // It will only be called once per request.
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -1294,7 +1281,7 @@ var (
// will get deleted before the process gracefully exits. // will get deleted before the process gracefully exits.
func PIDFile(filename string) error { func PIDFile(filename string) error {
pid := []byte(strconv.Itoa(os.Getpid()) + "\n") pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
err := os.WriteFile(filename, pid, 0600) err := os.WriteFile(filename, pid, 0o600)
if err != nil { if err != nil {
return err return err
} }

View file

@ -3,7 +3,7 @@ package caddy
import ( import (
"net/http" "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"
"github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
@ -15,13 +15,13 @@ func init() {
const ns, sub = "caddy", "admin" const ns, sub = "caddy", "admin"
adminMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{ adminMetrics.requests = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Namespace: ns,
Subsystem: sub, Subsystem: sub,
Name: "http_requests_total", Name: "http_requests_total",
Help: "Counter of requests made to the Admin API's HTTP endpoints.", Help: "Counter of requests made to the Admin API's HTTP endpoints.",
}, []string{"handler", "path", "code", "method"}) }, []string{"handler", "path", "code", "method"})
adminMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{ adminMetrics.errors = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Namespace: ns,
Subsystem: sub, Subsystem: sub,
Name: "http_request_errors_total", 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. // adminMetrics is a collection of metrics that can be tracked for the admin API.
var adminMetrics = struct { var adminMetrics = struct {
requestCount *prometheus.CounterVec requests *prometheus.CounterVec
requestErrors *prometheus.CounterVec errors *prometheus.CounterVec
}{} }{}
// Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names // instrumentAdminHandler wraps the handler with total and errored-out request count
// instead of lower-casing them. // in a manner similar to promhttp.InstrumentHandlerCounter. All errors are handled
// // using the passed error handler.
// Unlike promhttp.InstrumentHandlerCounter, this assumes a "code" and "method" func instrumentAdminHandler(pattern, handlerLabel string,
// label is present, and will panic otherwise. h AdminHandler, errorHandler func(http.ResponseWriter, *http.Request, error),
func instrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc { ) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { labels := prometheus.Labels{"path": pattern, "handler": handlerLabel}
d := newDelegator(w) requests := adminMetrics.requests.MustCurryWith(labels)
next.ServeHTTP(d, r) errors := adminMetrics.errors.MustCurryWith(labels)
counter.With(prometheus.Labels{
"code": metrics.SanitizeCode(d.status),
"method": metrics.SanitizeMethod(r.Method),
}).Inc()
})
}
func newDelegator(w http.ResponseWriter) *delegator { return func(w http.ResponseWriter, r *http.Request) {
return &delegator{ d := delegator{ResponseWriter: w}
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()
} }
} }