1
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-16 21:56:40 -05:00

metrics: move metrics up, outside servers (#6606)

* metrics: move `metrics`  up, outside `servers`

This change moves the metrics configuration from per-server level to a single config knob within the `http` app. Enabling `metrics` in any of the configured servers inside `http` enables metrics for all servers.

Fix #6604

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* normalize domain name

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
This commit is contained in:
Mohammed Al Sahaf 2024-10-18 18:54:21 +03:00 committed by GitHub
parent c6f2979986
commit 388c7e898c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 102 additions and 13 deletions

View file

@ -15,6 +15,7 @@
package httpcaddyfile package httpcaddyfile
import ( import (
"cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
@ -186,12 +187,25 @@ func (st ServerType) Setup(
return nil, warnings, err return nil, warnings, err
} }
// hoist the metrics config from per-server to global
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
for _, s := range servers {
if s.Metrics != nil {
metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
metrics = &caddyhttp.Metrics{
PerHost: metrics.PerHost || s.Metrics.PerHost,
}
s.Metrics = nil // we don't need it anymore
}
}
// now that each server is configured, make the HTTP app // now that each server is configured, make the HTTP app
httpApp := caddyhttp.App{ httpApp := caddyhttp.App{
HTTPPort: tryInt(options["http_port"], &warnings), HTTPPort: tryInt(options["http_port"], &warnings),
HTTPSPort: tryInt(options["https_port"], &warnings), HTTPSPort: tryInt(options["https_port"], &warnings),
GracePeriod: tryDuration(options["grace_period"], &warnings), GracePeriod: tryDuration(options["grace_period"], &warnings),
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings), ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
Metrics: metrics,
Servers: servers, Servers: servers,
} }

View file

@ -24,6 +24,7 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
) )
@ -53,6 +54,7 @@ func init() {
RegisterGlobalOption("local_certs", parseOptTrue) RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString) RegisterGlobalOption("key_type", parseOptSingleString)
RegisterGlobalOption("auto_https", parseOptAutoHTTPS) RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
RegisterGlobalOption("metrics", parseMetricsOptions)
RegisterGlobalOption("servers", parseServerOptions) RegisterGlobalOption("servers", parseServerOptions)
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions) RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
RegisterGlobalOption("cert_lifetime", parseOptDuration) RegisterGlobalOption("cert_lifetime", parseOptDuration)
@ -446,6 +448,24 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
return val, nil return val, nil
} }
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
d.Next() // consume option name
metrics := new(caddyhttp.Metrics)
for d.NextBlock(0) {
switch d.Val() {
case "per_host":
metrics.PerHost = true
default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
}
return metrics, nil
}
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileMetricsOptions(d)
}
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) { func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileServerOptions(d) return unmarshalCaddyfileServerOptions(d)
} }

View file

@ -240,6 +240,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
} }
case "metrics": case "metrics":
caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
serverOpts.Metrics = new(caddyhttp.Metrics) serverOpts.Metrics = new(caddyhttp.Metrics)
for nesting := d.Nesting(); d.NextBlock(nesting); { for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() { switch d.Val() {

View file

@ -0,0 +1,39 @@
{
metrics
servers :80 {
metrics {
per_host
}
}
}
:80 {
respond "Hello"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"body": "Hello",
"handler": "static_response"
}
]
}
]
}
},
"metrics": {
"per_host": true
}
}
}
}

View file

@ -26,12 +26,12 @@
} }
] ]
} }
], ]
}
},
"metrics": { "metrics": {
"per_host": true "per_host": true
} }
} }
} }
} }
}
}

View file

@ -15,6 +15,7 @@
package caddyhttp package caddyhttp
import ( import (
"cmp"
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
@ -142,6 +143,10 @@ type App struct {
// affect functionality. // affect functionality.
Servers map[string]*Server `json:"servers,omitempty"` Servers map[string]*Server `json:"servers,omitempty"`
// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
Metrics *Metrics `json:"metrics,omitempty"`
ctx caddy.Context ctx caddy.Context
logger *zap.Logger logger *zap.Logger
tlsApp *caddytls.TLS tlsApp *caddytls.TLS
@ -184,6 +189,10 @@ func (app *App) Provision(ctx caddy.Context) error {
return err return err
} }
if app.Metrics != nil {
app.Metrics.init = sync.Once{}
app.Metrics.httpMetrics = &httpMetrics{}
}
// prepare each server // prepare each server
oldContext := ctx.Context oldContext := ctx.Context
for srvName, srv := range app.Servers { for srvName, srv := range app.Servers {
@ -196,6 +205,15 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.errorLogger = app.logger.Named("log.error") srv.errorLogger = app.logger.Named("log.error")
srv.shutdownAtMu = new(sync.RWMutex) srv.shutdownAtMu = new(sync.RWMutex)
if srv.Metrics != nil {
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{
init: sync.Once{},
httpMetrics: &httpMetrics{},
})
app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost
}
// only enable access logs if configured // only enable access logs if configured
if srv.Logs != nil { if srv.Logs != nil {
srv.accessLogger = app.logger.Named("log.access") srv.accessLogger = app.logger.Named("log.access")
@ -342,16 +360,11 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...) srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
} }
} }
// pre-compile the primary handler chain, and be sure to wrap it in our // pre-compile the primary handler chain, and be sure to wrap it in our
// route handler so that important security checks are done, etc. // route handler so that important security checks are done, etc.
primaryRoute := emptyHandler primaryRoute := emptyHandler
if srv.Routes != nil { if srv.Routes != nil {
if srv.Metrics != nil { err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
srv.Metrics.init = sync.Once{}
srv.Metrics.httpMetrics = &httpMetrics{}
}
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
} }
@ -370,7 +383,7 @@ func (app *App) Provision(ctx caddy.Context) error {
// provision the named routes (they get compiled at runtime) // provision the named routes (they get compiled at runtime)
for name, route := range srv.NamedRoutes { for name, route := range srv.NamedRoutes {
err := route.Provision(ctx, srv.Metrics) err := route.Provision(ctx, app.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err) return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
@ -133,8 +134,8 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""} statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
if h.metrics.PerHost { if h.metrics.PerHost {
labels["host"] = r.Host labels["host"] = strings.ToLower(r.Host)
statusLabels["host"] = r.Host statusLabels["host"] = strings.ToLower(r.Host)
} }
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) inFlight := h.metrics.httpMetrics.requestInFlight.With(labels)

View file

@ -226,6 +226,7 @@ type Server struct {
// If set, metrics observations will be enabled. // If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change. // This setting is EXPERIMENTAL and subject to change.
// DEPRECATED: Use the app-level `metrics` field.
Metrics *Metrics `json:"metrics,omitempty"` Metrics *Metrics `json:"metrics,omitempty"`
name string name string