From 9389c190adb12d90682bdde09bc03e80b68ee003 Mon Sep 17 00:00:00 2001 From: gouthamve Date: Sun, 14 Apr 2024 13:07:43 +0200 Subject: [PATCH] Add fallback metric producer factory Signed-off-by: gouthamve --- exporters/autoexport/metrics.go | 15 ++++++++-- exporters/autoexport/metrics_test.go | 41 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/exporters/autoexport/metrics.go b/exporters/autoexport/metrics.go index 5117aa1c5d5..f15181eb571 100644 --- a/exporters/autoexport/metrics.go +++ b/exporters/autoexport/metrics.go @@ -79,6 +79,12 @@ func RegisterMetricProducer(name string, factory func(context.Context) (metric.P must(metricsProducers.registry.store(name, factory)) } +// WithFallbackMetricProducer sets the fallback producer to use when no producer +// is configured through the OTEL_METRICS_PRODUCERS environment variable. +func WithFallbackMetricProducer(producerFactory func(ctx context.Context) (metric.Producer, error)) { + metricsProducers.fallbackProducer = producerFactory +} + var ( metricsSignal = newSignal[metric.Reader]("OTEL_METRICS_EXPORTER") metricsProducers = newProducerRegistry("OTEL_METRICS_PRODUCERS") @@ -217,8 +223,9 @@ func getenv(key, fallback string) string { } type producerRegistry struct { - envKey string - registry *registry[metric.Producer] + envKey string + fallbackProducer func(context.Context) (metric.Producer, error) + registry *registry[metric.Producer] } func newProducerRegistry(envKey string) producerRegistry { @@ -233,6 +240,10 @@ func newProducerRegistry(envKey string) producerRegistry { func (pr producerRegistry) create(ctx context.Context) (metric.Producer, error) { expType := os.Getenv(pr.envKey) if expType == "" { + if pr.fallbackProducer != nil { + return pr.fallbackProducer(ctx) + } + return nil, nil } diff --git a/exporters/autoexport/metrics_test.go b/exporters/autoexport/metrics_test.go index a097946a079..9f5dc98290e 100644 --- a/exporters/autoexport/metrics_test.go +++ b/exporters/autoexport/metrics_test.go @@ -15,9 +15,11 @@ import ( "testing" "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp" + prometheusbridge "go.opentelemetry.io/contrib/bridges/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/metric" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "go.uber.org/goleak" ) @@ -192,3 +194,42 @@ func TestMetricProducerPrometheusWithPrometheusExporter(t *testing.T) { assert.NoError(t, mp.Shutdown(context.Background())) goleak.VerifyNone(t) } + +func TestMetricProducerFallbackWithPrometheusExporter(t *testing.T) { + assertNoOtelHandleErrors(t) + + reg := prometheus.NewRegistry() + someDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dummy_metric", + Help: "dummy metric", + }) + reg.MustRegister(someDummyMetric) + + WithFallbackMetricProducer(func(context.Context) (metric.Producer, error) { + return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg)), nil + }) + + t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") + t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") + + r, err := NewMetricReader(context.Background()) + assert.NoError(t, err) + + // pull-based exporters like Prometheus need to be registered + mp := metric.NewMeterProvider(metric.WithReader(r)) + + rws, ok := r.(readerWithServer) + if !ok { + t.Errorf("expected readerWithServer but got %v", r) + } + + resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) + assert.NoError(t, err) + body, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + + assert.Contains(t, string(body), "HELP dummy_metric_total dummy metric") + + assert.NoError(t, mp.Shutdown(context.Background())) + goleak.VerifyNone(t) +}