Skip to content

feat: Add new CollectorFunc utility #1724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
30 changes: 30 additions & 0 deletions prometheus/collectorfunc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 prometheus

// CollectorFunc is a convenient way to implement a Prometheus Collector
// without interface boilerplate.
// This implementation is based on DescribeByCollect method.
// familiarize yourself to it before using.
type CollectorFunc func(chan<- Metric)

// Collect calls the defined CollectorFunc function with the provided Metrics channel
func (f CollectorFunc) Collect(ch chan<- Metric) {
f(ch)
}

// Describe sends the descriptor information using DescribeByCollect
func (f CollectorFunc) Describe(ch chan<- *Desc) {
DescribeByCollect(f, ch)
}
83 changes: 83 additions & 0 deletions prometheus/collectorfunc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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 prometheus

import "testing"

func TestCollectorFunc(t *testing.T) {
testDesc := NewDesc(
"test_metric",
"A test metric",
nil, nil,
)

cf := CollectorFunc(func(ch chan<- Metric) {
ch <- MustNewConstMetric(
testDesc,
GaugeValue,
42.0,
)
})

ch := make(chan Metric, 1)
cf.Collect(ch)
close(ch)

metric := <-ch
if metric == nil {
t.Fatal("Expected metric, got nil")
}

descCh := make(chan *Desc, 1)
cf.Describe(descCh)
close(descCh)

desc := <-descCh
if desc == nil {
t.Fatal("Expected desc, got nil")
}

if desc.String() != testDesc.String() {
t.Fatalf("Expected %s, got %s", testDesc.String(), desc.String())
}
}

func TestCollectorFuncWithRegistry(t *testing.T) {
reg := NewPedanticRegistry()

cf := CollectorFunc(func(ch chan<- Metric) {
ch <- MustNewConstMetric(
NewDesc(
"test_metric",
"A test metric",
nil, nil,
),
GaugeValue,
42.0,
)
})

if err := reg.Register(cf); err != nil {
t.Errorf("Failed to register CollectorFunc: %v", err)
}

collectedMetrics, err := reg.Gather()
if err != nil {
t.Errorf("Failed to gather metrics: %v", err)
}

if len(collectedMetrics) != 1 {
t.Errorf("Expected 1 metric family, got %d", len(collectedMetrics))
}
}
48 changes: 48 additions & 0 deletions prometheus/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,51 @@ func ExampleNewConstMetricWithCreatedTimestamp() {
// Output:
// {"counter":{"value":1257894000,"createdTimestamp":"1970-01-01T00:00:00Z"}}
}

// Using CollectorFunc that registers the metric info for the HTTP requests.
func ExampleCollectorFunc() {
desc := prometheus.NewDesc(
"http_requests_info",
"Information about the received HTTP requests.",
[]string{"code", "method"},
nil,
)

// Example 1: 42 GET requests with 200 OK status code.
collector := prometheus.CollectorFunc(func(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
desc,
prometheus.CounterValue, // Metric type: Counter
42, // Value
"200", // Label value: HTTP status code
"GET", // Label value: HTTP method
)

// Example 2: 15 POST requests with 404 Not Found status code.
ch <- prometheus.MustNewConstMetric(
desc,
prometheus.CounterValue,
15,
"404",
"POST",
)
})

prometheus.MustRegister(collector)

// Just for demonstration, let's check the state of the metric by registering
// it with a custom registry and then let it collect the metrics.

reg := prometheus.NewRegistry()
reg.MustRegister(collector)

metricFamilies, err := reg.Gather()
if err != nil || len(metricFamilies) != 1 {
panic("unexpected behavior of custom test registry")
}

fmt.Println(toNormalizedJSON(sanitizeMetricFamily(metricFamilies[0])))

// Output:
// {"name":"http_requests_info","help":"Information about the received HTTP requests.","type":"COUNTER","metric":[{"label":[{"name":"code","value":"200"},{"name":"method","value":"GET"}],"counter":{"value":42}},{"label":[{"name":"code","value":"404"},{"name":"method","value":"POST"}],"counter":{"value":15}}]}
}
Loading