1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00

Refactor Prometheus exporter (#3239)

This change will automatically register a created exporter with a prometheus registerer, if none are provided it will use prometheus' DefaultRegisterer.

Co-authored-by: Chester Cheung <cheung.zhy.csu@gmail.com>
This commit is contained in:
Aaron Clawson
2022-10-14 09:22:43 -05:00
committed by GitHub
parent 1e72af45a9
commit b6a22abab7
8 changed files with 171 additions and 57 deletions
+5
View File
@@ -8,9 +8,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
### Added
- Prometheus exporter will register with a prometheus registerer on creation, there are options to control this. (#3239)
### Changed
- `sdktrace.TraceProvider.Shutdown` and `sdktrace.TraceProvider.ForceFlush` to not return error when no processor register. (#3268)
- The `"go.opentelemetry.io/otel/exporters/prometheus".New` now also returns an error indicating the failure to register the exporter with Prometheus. (#3239)
### Fixed
+10 -15
View File
@@ -22,11 +22,10 @@ import (
"os"
"os/signal"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/attribute"
otelprom "go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/sdk/metric"
)
@@ -37,12 +36,15 @@ func main() {
// The exporter embeds a default OpenTelemetry Reader and
// implements prometheus.Collector, allowing it to be used as
// both a Reader and Collector.
exporter := otelprom.New()
exporter, err := prometheus.New()
if err != nil {
log.Fatal(err)
}
provider := metric.NewMeterProvider(metric.WithReader(exporter))
meter := provider.Meter("github.com/open-telemetry/opentelemetry-go/example/prometheus")
// Start the prometheus HTTP server and pass the exporter Collector to it
go serveMetrics(exporter.Collector)
go serveMetrics()
attrs := []attribute.KeyValue{
attribute.Key("A").String("B"),
@@ -77,17 +79,10 @@ func main() {
<-ctx.Done()
}
func serveMetrics(collector prometheus.Collector) {
registry := prometheus.NewRegistry()
err := registry.Register(collector)
if err != nil {
fmt.Printf("error registering collector: %v", err)
return
}
log.Printf("serving metrics at localhost:2222/metrics")
http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
err = http.ListenAndServe(":2222", nil)
func serveMetrics() {
log.Printf("serving metrics at localhost:2223/metrics")
http.Handle("/metrics", promhttp.Handler())
err := http.ListenAndServe(":2223", nil)
if err != nil {
fmt.Printf("error serving http: %v", err)
return
+8 -13
View File
@@ -22,7 +22,6 @@ import (
"os"
"os/signal"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/attribute"
@@ -40,7 +39,10 @@ func main() {
ctx := context.Background()
// The exporter embeds a default OpenTelemetry Reader, allowing it to be used in WithReader.
exporter := otelprom.New()
exporter, err := otelprom.New()
if err != nil {
log.Fatal(err)
}
// View to customize histogram buckets and rename a single histogram instrument.
customBucketsView, err := view.New(
@@ -68,7 +70,7 @@ func main() {
meter := provider.Meter(meterName)
// Start the prometheus HTTP server and pass the exporter Collector to it
go serveMetrics(exporter.Collector)
go serveMetrics()
attrs := []attribute.KeyValue{
attribute.Key("A").String("B"),
@@ -94,17 +96,10 @@ func main() {
<-ctx.Done()
}
func serveMetrics(collector prometheus.Collector) {
registry := prometheus.NewRegistry()
err := registry.Register(collector)
if err != nil {
fmt.Printf("error registering collector: %v", err)
return
}
func serveMetrics() {
log.Printf("serving metrics at localhost:2222/metrics")
http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
err = http.ListenAndServe(":2222", nil)
http.Handle("/metrics", promhttp.Handler())
err := http.ListenAndServe(":2222", nil)
if err != nil {
fmt.Printf("error serving http: %v", err)
return
+3 -5
View File
@@ -27,14 +27,12 @@ import (
func benchmarkCollect(b *testing.B, n int) {
ctx := context.Background()
exporter := New()
registry := prometheus.NewRegistry()
exporter, err := New(WithRegisterer(registry))
require.NoError(b, err)
provider := metric.NewMeterProvider(metric.WithReader(exporter))
meter := provider.Meter("testmeter")
registry := prometheus.NewRegistry()
err := registry.Register(exporter.Collector)
require.NoError(b, err)
for i := 0; i < n; i++ {
counter, err := meter.SyncFloat64().Counter(fmt.Sprintf("foo_%d", i))
require.NoError(b, err)
+60
View File
@@ -0,0 +1,60 @@
// Copyright The OpenTelemetry 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 "go.opentelemetry.io/otel/exporters/prometheus"
import (
"testing"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
)
func TestNewConfig(t *testing.T) {
registry := prometheus.NewRegistry()
testCases := []struct {
name string
options []Option
wantRegisterer prometheus.Registerer
}{
{
name: "Default",
options: nil,
wantRegisterer: prometheus.DefaultRegisterer,
},
{
name: "WithRegisterer",
options: []Option{
WithRegisterer(registry),
},
wantRegisterer: registry,
},
{
name: "nil options do nothing",
options: []Option{
WithRegisterer(nil),
},
wantRegisterer: prometheus.DefaultRegisterer,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfg := newConfig(tt.options...)
assert.Equal(t, tt.wantRegisterer, cfg.registerer)
})
}
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright The OpenTelemetry 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 "go.opentelemetry.io/otel/exporters/prometheus"
import (
"github.com/prometheus/client_golang/prometheus"
)
// config contains options for the exporter.
type config struct {
registerer prometheus.Registerer
}
// newConfig creates a validated config configured with options.
func newConfig(opts ...Option) config {
cfg := config{}
for _, opt := range opts {
cfg = opt.apply(cfg)
}
if cfg.registerer == nil {
cfg.registerer = prometheus.DefaultRegisterer
}
return cfg
}
// Option sets exporter option values.
type Option interface {
apply(config) config
}
type optionFunc func(config) config
func (fn optionFunc) apply(cfg config) config {
return fn(cfg)
}
// WithRegisterer configures which prometheus Registerer the Exporter will
// register with. If no registerer is used the prometheus DefaultRegisterer is
// used.
func WithRegisterer(reg prometheus.Registerer) Option {
return optionFunc(func(cfg config) config {
cfg.registerer = reg
return cfg
})
}
+23 -19
View File
@@ -16,6 +16,7 @@ package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
import (
"context"
"fmt"
"sort"
"strings"
"unicode"
@@ -33,39 +34,42 @@ import (
// interface for easy instantiation with a MeterProvider.
type Exporter struct {
metric.Reader
Collector prometheus.Collector
}
var _ metric.Reader = &Exporter{}
// collector is used to implement prometheus.Collector.
type collector struct {
metric.Reader
}
// config is added here to allow for options expansion in the future.
type config struct{}
// Option may be used in the future to apply options to a Prometheus Exporter config.
type Option interface {
apply(config) config
reader metric.Reader
}
// New returns a Prometheus Exporter.
func New(_ ...Option) Exporter {
func New(opts ...Option) (*Exporter, error) {
cfg := newConfig(opts...)
// this assumes that the default temporality selector will always return cumulative.
// we only support cumulative temporality, so building our own reader enforces this.
// TODO (#3244): Enable some way to configure the reader, but not change temporality.
reader := metric.NewManualReader()
e := Exporter{
Reader: reader,
Collector: &collector{
Reader: reader,
},
collector := &collector{
reader: reader,
}
return e
if err := cfg.registerer.Register(collector); err != nil {
return nil, fmt.Errorf("cannot register the collector: %w", err)
}
e := &Exporter{
Reader: reader,
}
return e, nil
}
// Describe implements prometheus.Collector.
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
metrics, err := c.Reader.Collect(context.TODO())
metrics, err := c.reader.Collect(context.TODO())
if err != nil {
otel.Handle(err)
}
@@ -76,7 +80,7 @@ func (c *collector) Describe(ch chan<- *prometheus.Desc) {
// Collect implements prometheus.Collector.
func (c *collector) Collect(ch chan<- prometheus.Metric) {
metrics, err := c.Reader.Collect(context.TODO())
metrics, err := c.reader.Collect(context.TODO())
if err != nil {
otel.Handle(err)
}
+3 -5
View File
@@ -137,8 +137,10 @@ func TestPrometheusExporter(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
registry := prometheus.NewRegistry()
exporter := New()
exporter, err := New(WithRegisterer(registry))
require.NoError(t, err)
customBucketsView, err := view.New(
view.MatchInstrumentName("histogram_*"),
@@ -153,10 +155,6 @@ func TestPrometheusExporter(t *testing.T) {
provider := metric.NewMeterProvider(metric.WithReader(exporter, customBucketsView, defaultView))
meter := provider.Meter("testmeter")
registry := prometheus.NewRegistry()
err = registry.Register(exporter.Collector)
require.NoError(t, err)
tc.recordMetrics(ctx, meter)
file, err := os.Open(tc.expectedFile)