just swallow the duplicate registration error

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
This commit is contained in:
Mohammed Al Sahaf 2025-06-09 22:00:18 +03:00
parent e6eba2b0ed
commit 87309aaaba
No known key found for this signature in database
4 changed files with 35 additions and 28 deletions

View File

@ -21,7 +21,6 @@ import (
"log" "log"
"log/slog" "log/slog"
"reflect" "reflect"
"sync"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -63,7 +62,7 @@ type Context struct {
// See standard library context package's documentation. // See standard library context package's documentation.
func NewContext(ctx Context) (Context, context.CancelFunc) { func NewContext(ctx Context) (Context, context.CancelFunc) {
r := prometheus.NewPedanticRegistry() r := prometheus.NewPedanticRegistry()
newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg, metricsRegistry: &registryGatherer{registry: r, gatherer: r, tracker: make(map[string]*sync.Once)}} newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg, metricsRegistry: &registryGatherer{registry: r, gatherer: r}}
c, cancel := context.WithCancel(ctx.Context) c, cancel := context.WithCancel(ctx.Context)
wrappedCancel := func() { wrappedCancel := func() {
cancel() cancel()
@ -105,10 +104,7 @@ func (ctx *Context) FileSystems() FileSystems {
// Returns the active metrics registry for the context // Returns the active metrics registry for the context
// EXPERIMENTAL: This API is subject to change. // EXPERIMENTAL: This API is subject to change.
func (ctx *Context) GetMetricsRegistry() RegistererGatherer { func (ctx *Context) GetMetricsRegistry() MetricsRegistererGatherer {
if ctx.Module() != nil {
ctx.metricsRegistry.callerModule = ctx.Module().CaddyModule().String()
}
return ctx.metricsRegistry return ctx.metricsRegistry
} }

View File

@ -1,8 +1,8 @@
package caddy package caddy
import ( import (
"errors"
"net/http" "net/http"
"sync"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
io_prometheus_client "github.com/prometheus/client_model/go" io_prometheus_client "github.com/prometheus/client_model/go"
@ -85,16 +85,13 @@ func (d *delegator) Unwrap() http.ResponseWriter {
return d.ResponseWriter return d.ResponseWriter
} }
type RegistererGatherer interface { type MetricsRegistererGatherer interface {
prometheus.Registerer prometheus.Registerer
prometheus.Gatherer prometheus.Gatherer
} }
type registryGatherer struct { type registryGatherer struct {
registry prometheus.Registerer registry prometheus.Registerer
gatherer prometheus.Gatherer gatherer prometheus.Gatherer
tracker map[string]*sync.Once
callerModule string
} }
// Gather implements prometheus.Gatherer. // Gather implements prometheus.Gatherer.
@ -102,31 +99,45 @@ func (r *registryGatherer) Gather() ([]*io_prometheus_client.MetricFamily, error
return r.gatherer.Gather() return r.gatherer.Gather()
} }
// MustRegister implements prometheus.Registerer. // MustRegister calls `MustRegister` on the backing registry one collector
// at a time to capture the module at which the call may have panicked. Panics
// of duplicate registration are ignored.
func (r *registryGatherer) MustRegister(cs ...prometheus.Collector) { func (r *registryGatherer) MustRegister(cs ...prometheus.Collector) {
if _, ok := r.tracker[r.callerModule]; !ok { var current prometheus.Collector
r.tracker[r.callerModule] = &sync.Once{} defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
panic(r)
}
if !errors.Is(err, prometheus.AlreadyRegisteredError{
ExistingCollector: current,
NewCollector: current,
}) {
panic(err)
}
}
}()
for _, current = range cs {
r.registry.MustRegister(current)
} }
r.tracker[r.callerModule].Do(func() {
r.registry.MustRegister(cs...)
})
} }
// Register implements prometheus.Registerer. // Register implements prometheus.Registerer. Errors of duplicate registration
// are ignored.
func (r *registryGatherer) Register(c prometheus.Collector) error { func (r *registryGatherer) Register(c prometheus.Collector) error {
var err error if err := r.registry.Register(c); err != nil &&
if _, ok := r.tracker[r.callerModule]; !ok { !errors.Is(err, prometheus.AlreadyRegisteredError{
r.tracker[r.callerModule] = &sync.Once{} ExistingCollector: c,
NewCollector: c,
}) {
return err
} }
r.tracker[r.callerModule].Do(func() { return nil
err = r.registry.Register(c)
})
return err
} }
// Unregister implements prometheus.Registerer. // Unregister implements prometheus.Registerer.
func (r *registryGatherer) Unregister(c prometheus.Collector) bool { func (r *registryGatherer) Unregister(c prometheus.Collector) bool {
delete(r.tracker, r.callerModule)
return r.registry.Unregister(c) return r.registry.Unregister(c)
} }

View File

@ -31,7 +31,7 @@ func init() {
// See the Metrics module for a configurable endpoint that is usable if the // See the Metrics module for a configurable endpoint that is usable if the
// Admin API is disabled. // Admin API is disabled.
type AdminMetrics struct { type AdminMetrics struct {
registry caddy.RegistererGatherer registry caddy.MetricsRegistererGatherer
metricsHandler http.Handler metricsHandler http.Handler
} }

View File

@ -107,7 +107,7 @@ var (
_ caddyfile.Unmarshaler = (*Metrics)(nil) _ caddyfile.Unmarshaler = (*Metrics)(nil)
) )
func createMetricsHandler(logger promhttp.Logger, enableOpenMetrics bool, registry caddy.RegistererGatherer) http.Handler { func createMetricsHandler(logger promhttp.Logger, enableOpenMetrics bool, registry caddy.MetricsRegistererGatherer) http.Handler {
return promhttp.InstrumentMetricHandler(registry, return promhttp.InstrumentMetricHandler(registry,
promhttp.HandlerFor(registry, promhttp.HandlerOpts{ promhttp.HandlerFor(registry, promhttp.HandlerOpts{
// will only log errors if logger is non-nil // will only log errors if logger is non-nil