From 74547f5bedafcb59fc25f14b43301f33a40f9416 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Fri, 16 Sep 2022 13:32:49 -0600 Subject: caddyhttp: Make metrics opt-in (#5042) * caddyhttp: Make metrics opt-in Related to #4644 * Make configurable in Caddyfile --- caddyconfig/httpcaddyfile/serveroptions.go | 11 +++++++++++ modules/caddyhttp/app.go | 2 +- modules/caddyhttp/metrics.go | 4 ++++ modules/caddyhttp/routes.go | 17 ++++++++++------- modules/caddyhttp/server.go | 4 ++++ 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index f3e3d73..8e021fd 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -43,6 +43,7 @@ type serverOptions struct { Protocols []string StrictSNIHost *bool ShouldLogCredentials bool + Metrics *caddyhttp.Metrics } func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { @@ -175,6 +176,15 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { } serverOpts.StrictSNIHost = &boolVal + case "metrics": + if d.NextArg() { + return nil, d.ArgErr() + } + if d.NextBlock(0) { + return nil, d.ArgErr() + } + serverOpts.Metrics = new(caddyhttp.Metrics) + // TODO: DEPRECATED. (August 2022) case "protocol": caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon") @@ -259,6 +269,7 @@ func applyServerOptions( server.MaxHeaderBytes = opts.MaxHeaderBytes server.Protocols = opts.Protocols server.StrictSNIHost = opts.StrictSNIHost + server.Metrics = opts.Metrics if opts.ShouldLogCredentials { if server.Logs == nil { server.Logs = &caddyhttp.ServerLogConfig{} diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index e48f828..3edc5b2 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -263,7 +263,7 @@ func (app *App) Provision(ctx caddy.Context) error { // route handler so that important security checks are done, etc. primaryRoute := emptyHandler if srv.Routes != nil { - err := srv.Routes.ProvisionHandlers(ctx) + err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics) if err != nil { return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) } diff --git a/modules/caddyhttp/metrics.go b/modules/caddyhttp/metrics.go index 458c22a..64fbed7 100644 --- a/modules/caddyhttp/metrics.go +++ b/modules/caddyhttp/metrics.go @@ -11,6 +11,10 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) +// Metrics configures metrics observations. +// EXPERIMENTAL and subject to change or removal. +type Metrics struct{} + var httpMetrics = struct { init sync.Once requestInFlight *prometheus.GaugeVec diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index ce9bece..da25097 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -130,7 +130,7 @@ func (routes RouteList) Provision(ctx caddy.Context) error { if err != nil { return err } - return routes.ProvisionHandlers(ctx) + return routes.ProvisionHandlers(ctx, nil) } // ProvisionMatchers sets up all the matchers by loading the @@ -156,7 +156,7 @@ func (routes RouteList) ProvisionMatchers(ctx caddy.Context) error { // handler modules. Only call this method directly if you need // to set up matchers and handlers separately without having // to provision a second time; otherwise use Provision instead. -func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error { +func (routes RouteList) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error { for i := range routes { handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw") if err != nil { @@ -168,7 +168,7 @@ func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error { // pre-compile the middleware handler chain for _, midhandler := range routes[i].Handlers { - routes[i].middleware = append(routes[i].middleware, wrapMiddleware(ctx, midhandler)) + routes[i].middleware = append(routes[i].middleware, wrapMiddleware(ctx, midhandler, metrics)) } } return nil @@ -270,9 +270,12 @@ func wrapRoute(route Route) Middleware { // we need to pull this particular MiddlewareHandler // pointer into its own stack frame to preserve it so it // won't be overwritten in future loop iterations. -func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware { - // wrap the middleware with metrics instrumentation - metricsHandler := newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh) +func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { + handlerToUse := mh + if metrics != nil { + // wrap the middleware with metrics instrumentation + handlerToUse = newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh) + } return func(next Handler) Handler { // copy the next handler (it's an interface, so it's @@ -284,7 +287,7 @@ func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware { return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { // TODO: This is where request tracing could be implemented // TODO: see what the std lib gives us in terms of stack tracing too - return metricsHandler.ServeHTTP(w, r, nextCopy) + return handlerToUse.ServeHTTP(w, r, nextCopy) }) } } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 83f1a53..ca5a594 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -152,6 +152,10 @@ type Server struct { // Default: `[h1 h2 h3]` Protocols []string `json:"protocols,omitempty"` + // If set, metrics observations will be enabled. + // This setting is EXPERIMENTAL and subject to change. + Metrics *Metrics `json:"metrics,omitempty"` + name string primaryHandlerChain Handler -- cgit v1.2.3