Prometheus: Client: Golang: Prevent Duplicate Registration

 2nd September 2022 at 10:22am

Prometheus 的 Golang SDK 中,使用 promauto.NewCounterVec 类函数时,会自动把指标注册进默认 Registerer。如果两个指标重复注册,会报错 panic:

// a.go
var (
    dbPingCounter = promauto.NewCounterVec(prometheus.CounterOpts{
			Name: "db_ping",
		}, []string{
			"dbname",
			"result",
		})
)

// b.go
var (
    dbPingCounter = promauto.NewCounterVec(prometheus.CounterOpts{
			Name: "db_ping",
		}, []string{
			"dbname",
			"result",
		})
)
// 如果 b.go 晚于 a.go 被 import,运行 promauto.NewCounterVec 时会 panic

遇到这个场景的原因是,我要将一个包的子包单独拆分出来;由于包的用户在迁移过程可能同时用新旧两个包,而两个包中有同样的指标(name 及 labels 一致),会遇到上述的问题。

一个解决办法是,不用 proauto,自行将 Collector 注册进 Register,并判断返回的错误:

func TryRegisterPromCounterVec(collector *prometheus.CounterVec) (*prometheus.CounterVec, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector *prometheus.CounterVec
			)
			if existingCollector, castOK = are.ExistingCollector.(*prometheus.CounterVec); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not *prometheus.CounterVec")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromCounter(collector prometheus.Counter) (prometheus.Counter, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector prometheus.Counter
			)
			if existingCollector, castOK = are.ExistingCollector.(prometheus.Counter); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not prometheus.Counter")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromGaugeVec(collector *prometheus.GaugeVec) (*prometheus.GaugeVec, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector *prometheus.GaugeVec
			)
			if existingCollector, castOK = are.ExistingCollector.(*prometheus.GaugeVec); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not *prometheus.GaugeVec")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromGauge(collector prometheus.Gauge) (prometheus.Gauge, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector prometheus.Gauge
			)
			if existingCollector, castOK = are.ExistingCollector.(prometheus.Gauge); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not prometheus.Gauge")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromHistogramVec(collector *prometheus.HistogramVec) (*prometheus.HistogramVec, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector *prometheus.HistogramVec
			)
			if existingCollector, castOK = are.ExistingCollector.(*prometheus.HistogramVec); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not *prometheus.HistogramVec")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromHistogram(collector prometheus.Histogram) (prometheus.Histogram, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector prometheus.Histogram
			)
			if existingCollector, castOK = are.ExistingCollector.(prometheus.Histogram); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not prometheus.Histogram")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromSummaryVec(collector *prometheus.SummaryVec) (*prometheus.SummaryVec, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector *prometheus.SummaryVec
			)
			if existingCollector, castOK = are.ExistingCollector.(*prometheus.SummaryVec); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not *prometheus.SummaryVec")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}

func TryRegisterPromSummary(collector prometheus.Summary) (prometheus.Summary, error) {
	if err := prometheus.Register(collector); err != nil {
		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
			var (
				castOK            bool
				existingCollector prometheus.Summary
			)
			if existingCollector, castOK = are.ExistingCollector.(prometheus.Summary); !castOK {
				return nil, fmt.Errorf("collector already registered but it's not prometheus.Summary")
			}
			return existingCollector, nil
		}
		return nil, fmt.Errorf("register collector failed: %w", err)
	}
	return collector, nil
}