package reverseproxy import ( "errors" "runtime/debug" "sync" "time" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" ) var reverseProxyMetrics = struct { once sync.Once upstreamsHealthy *prometheus.GaugeVec logger *zap.Logger }{} func initReverseProxyMetrics(handler *Handler, registry *prometheus.Registry) { const ns, sub = "caddy", "reverse_proxy" upstreamsLabels := []string{"upstream"} reverseProxyMetrics.once.Do(func() { reverseProxyMetrics.upstreamsHealthy = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: ns, Subsystem: sub, Name: "upstreams_healthy", Help: "Health status of reverse proxy upstreams.", }, upstreamsLabels) }) // duplicate registration could happen if multiple sites with reverse proxy are configured; so ignore the error because // there's no good way to capture having multiple sites with reverse proxy. If this happens, the metrics will be // registered twice, but the second registration will be ignored. if err := registry.Register(reverseProxyMetrics.upstreamsHealthy); err != nil && !errors.Is(err, prometheus.AlreadyRegisteredError{ ExistingCollector: reverseProxyMetrics.upstreamsHealthy, NewCollector: reverseProxyMetrics.upstreamsHealthy, }) { panic(err) } reverseProxyMetrics.logger = handler.logger.Named("reverse_proxy.metrics") } type metricsUpstreamsHealthyUpdater struct { handler *Handler } func newMetricsUpstreamsHealthyUpdater(handler *Handler, ctx caddy.Context) *metricsUpstreamsHealthyUpdater { initReverseProxyMetrics(handler, ctx.GetMetricsRegistry()) reverseProxyMetrics.upstreamsHealthy.Reset() return &metricsUpstreamsHealthyUpdater{handler} } func (m *metricsUpstreamsHealthyUpdater) init() { go func() { defer func() { if err := recover(); err != nil { if c := reverseProxyMetrics.logger.Check(zapcore.ErrorLevel, "upstreams healthy metrics updater panicked"); c != nil { c.Write( zap.Any("error", err), zap.ByteString("stack", debug.Stack()), ) } } }() m.update() ticker := time.NewTicker(10 * time.Second) for { select { case <-ticker.C: m.update() case <-m.handler.ctx.Done(): ticker.Stop() return } } }() } func (m *metricsUpstreamsHealthyUpdater) update() { for _, upstream := range m.handler.Upstreams { labels := prometheus.Labels{"upstream": upstream.Dial} gaugeValue := 0.0 if upstream.Healthy() { gaugeValue = 1.0 } reverseProxyMetrics.upstreamsHealthy.With(labels).Set(gaugeValue) } }