From a8cbb1f8d449eec77079ff763fd4e43e60301019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Mon, 25 Aug 2025 11:07:54 +0200 Subject: [PATCH] collectors/version: Allow for custom label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-by: bwplotka Signed-off-by: Manuel RĂ¼ger --- prometheus/collectors/version/version.go | 41 +++++-- prometheus/collectors/version/version_test.go | 114 ++++++++++++++++++ 2 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 prometheus/collectors/version/version_test.go diff --git a/prometheus/collectors/version/version.go b/prometheus/collectors/version/version.go index c96e18712..0306b5878 100644 --- a/prometheus/collectors/version/version.go +++ b/prometheus/collectors/version/version.go @@ -15,15 +15,44 @@ package version import ( "fmt" + "maps" "github.com/prometheus/common/version" "github.com/prometheus/client_golang/prometheus" ) +type Option func(*options) + +type options struct { + extraConstLabels prometheus.Labels +} + +func WithExtraConstLabels(l prometheus.Labels) Option { + return func(o *options) { + o.extraConstLabels = l + } +} + // NewCollector returns a collector that exports metrics about current version // information. -func NewCollector(program string) prometheus.Collector { +func NewCollector(program string, opts ...Option) prometheus.Collector { + o := options{} + for _, opt := range opts { + opt(&o) + } + + constLabels := prometheus.Labels{ + "version": version.Version, + "revision": version.GetRevision(), + "branch": version.Branch, + "goversion": version.GoVersion, + "goos": version.GoOS, + "goarch": version.GoArch, + "tags": version.GetTags(), + } + maps.Copy(constLabels, o.extraConstLabels) + return prometheus.NewGaugeFunc( prometheus.GaugeOpts{ Namespace: program, @@ -32,15 +61,7 @@ func NewCollector(program string) prometheus.Collector { "A metric with a constant '1' value labeled by version, revision, branch, goversion from which %s was built, and the goos and goarch for the build.", program, ), - ConstLabels: prometheus.Labels{ - "version": version.Version, - "revision": version.GetRevision(), - "branch": version.Branch, - "goversion": version.GoVersion, - "goos": version.GoOS, - "goarch": version.GoArch, - "tags": version.GetTags(), - }, + ConstLabels: constLabels, }, func() float64 { return 1 }, ) diff --git a/prometheus/collectors/version/version_test.go b/prometheus/collectors/version/version_test.go new file mode 100644 index 000000000..ec4d308ee --- /dev/null +++ b/prometheus/collectors/version/version_test.go @@ -0,0 +1,114 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/prometheus/client_golang/prometheus" +) + +var defaultLabels = []string{"branch", "goarch", "goos", "goversion", "revision", "tags", "version"} + +func TestGoVersionCollector(t *testing.T) { + reg := prometheus.NewPedanticRegistry() + reg.MustRegister(NewCollector( + "foo"), + ) + result, err := reg.Gather() + if err != nil { + t.Fatal(err) + } + + if _, err := json.Marshal(result); err != nil { + t.Errorf("json marshalling should not fail, %v", err) + } + + got := []string{} + + for _, r := range result { + got = append(got, r.GetName()) + fmt.Println("foo") + m := r.GetMetric() + + if len(m) != 1 { + t.Errorf("expected 1 metric, but got %d", len(m)) + } + + lk := []string{} + for _, lp := range m[0].GetLabel() { + lk = append(lk, lp.GetName()) + } + + if diff := cmp.Diff(lk, defaultLabels); diff != "" { + t.Errorf("missmatch (-want +got):\n%s", diff) + } + + } + + if diff := cmp.Diff(got, []string{"foo_build_info"}); diff != "" { + t.Errorf("missmatch (-want +got):\n%s", diff) + } +} + +func TestGoVersionCollectorWithLabels(t *testing.T) { + reg := prometheus.NewPedanticRegistry() + + labels := prometheus.Labels{ + "z-mylabel": "myvalue", + } + reg.MustRegister(NewCollector( + "foo", WithExtraConstLabels(labels)), + ) + result, err := reg.Gather() + if err != nil { + t.Fatal(err) + } + + if _, err := json.Marshal(result); err != nil { + t.Errorf("json marshalling should not fail, %v", err) + } + + got := []string{} + + for _, r := range result { + got = append(got, r.GetName()) + fmt.Println("foo") + m := r.GetMetric() + + if len(m) != 1 { + t.Errorf("expected 1 metric, but got %d", len(m)) + } + + lk := []string{} + for _, lp := range m[0].GetLabel() { + lk = append(lk, lp.GetName()) + } + + labels := append(defaultLabels, "z-mylabel") + + if diff := cmp.Diff(lk, labels); diff != "" { + t.Errorf("missmatch (-want +got):\n%s", diff) + } + + } + + if diff := cmp.Diff(got, []string{"foo_build_info"}); diff != "" { + t.Errorf("missmatch (-want +got):\n%s", diff) + } +}