From c2e920394f332fe2dce1ff2d40cee1790effad0a Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 8 Dec 2025 19:18:22 +0000 Subject: [PATCH 1/6] prometheus exporter ignores metrics from the Prometheus bridge --- exporters/prometheus/exporter.go | 8 ++++ exporters/prometheus/exporter_test.go | 40 +++++++++++++++++++ .../prometheus/testdata/just_target_info.txt | 3 ++ 3 files changed, 51 insertions(+) create mode 100755 exporters/prometheus/testdata/just_target_info.txt diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index d73786b83ed..c7b4c78cf75 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -35,6 +35,11 @@ const ( scopeNameLabel = scopeLabelPrefix + "name" scopeVersionLabel = scopeLabelPrefix + "version" scopeSchemaLabel = scopeLabelPrefix + "schema_url" + // metrics from the prometehus bridge are ignored because this produces + // errors. Users should directly register prometheus metrics with the + // Registerer, rather than round-tripping them through the bridge and + // exporter. + bridgeScopeName = "go.opentelemetry.io/contrib/bridges/prometheus" ) var metricsPool = sync.Pool{ @@ -229,6 +234,9 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { } for j, scopeMetrics := range metrics.ScopeMetrics { + if scopeMetrics.Scope.Name == bridgeScopeName { + continue + } n := len(c.resourceKeyVals.keys) + 2 // resource attrs + scope name + scope version kv := keyVals{ keys: make([]string, 0, n), diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index 2c28bb1dbda..c773583496a 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -797,6 +797,46 @@ func TestMultiScopes(t *testing.T) { require.NoError(t, err) } +func TestBridgeScopeIgnored(t *testing.T) { + ctx := t.Context() + registry := prometheus.NewRegistry() + exporter, err := New( + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + WithRegisterer(registry), + ) + require.NoError(t, err) + + res, err := resource.New(ctx, + // always specify service.name because the default depends on the running OS + resource.WithAttributes(semconv.ServiceName("prometheus_test")), + // Overwrite the semconv.TelemetrySDKVersionKey value so we don't need to update every version + resource.WithAttributes(semconv.TelemetrySDKVersion("latest")), + ) + require.NoError(t, err) + res, err = resource.Merge(resource.Default(), res) + require.NoError(t, err) + + provider := metric.NewMeterProvider( + metric.WithReader(exporter), + metric.WithResource(res), + ) + + fooCounter, err := provider.Meter(bridgeScopeName, otelmetric.WithInstrumentationVersion("v0.1.0")). + Int64Counter( + "foo", + otelmetric.WithUnit("s"), + otelmetric.WithDescription("meter foo counter")) + assert.NoError(t, err) + fooCounter.Add(ctx, 100, otelmetric.WithAttributes(attribute.String("type", "foo"))) + + file, err := os.Open("testdata/just_target_info.txt") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, file.Close()) }) + + err = testutil.GatherAndCompare(registry, file) + require.NoError(t, err) +} + func TestDuplicateMetrics(t *testing.T) { ab := attribute.NewSet(attribute.String("A", "B")) withAB := otelmetric.WithAttributeSet(ab) diff --git a/exporters/prometheus/testdata/just_target_info.txt b/exporters/prometheus/testdata/just_target_info.txt new file mode 100755 index 00000000000..9ceaae8fc87 --- /dev/null +++ b/exporters/prometheus/testdata/just_target_info.txt @@ -0,0 +1,3 @@ +# HELP target_info Target metadata +# TYPE target_info gauge +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 From 131c6be8a2ada2faa91ec6c706d91aafeba9b613 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 8 Dec 2025 19:35:34 +0000 Subject: [PATCH 2/6] log a single error log when using the Prometheus bridge and exporter together --- exporters/prometheus/errors.go | 7 ++++--- exporters/prometheus/exporter.go | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/exporters/prometheus/errors.go b/exporters/prometheus/errors.go index f2076de761e..9b6e5c42d4e 100644 --- a/exporters/prometheus/errors.go +++ b/exporters/prometheus/errors.go @@ -7,7 +7,8 @@ import "errors" // Sentinel errors for consistent error checks in tests. var ( - errInvalidMetricType = errors.New("invalid metric type") - errInvalidMetric = errors.New("invalid metric") - errEHScaleBelowMin = errors.New("exponential histogram scale below minimum supported") + errInvalidMetricType = errors.New("invalid metric type") + errInvalidMetric = errors.New("invalid metric") + errEHScaleBelowMin = errors.New("exponential histogram scale below minimum supported") + errBridgeNotSupported = errors.New("metrics from the Prometheus Bridge are not supproted in the Prometheus exporter, and will be skipped") ) diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index c7b4c78cf75..0f2259af381 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -101,6 +101,8 @@ type collector struct { unitNamer otlptranslator.UnitNamer inst *observ.Instrumentation + + bridgeErrorOnce sync.Once } // New returns a Prometheus Exporter. @@ -235,6 +237,9 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { for j, scopeMetrics := range metrics.ScopeMetrics { if scopeMetrics.Scope.Name == bridgeScopeName { + c.bridgeErrorOnce.Do(func() { + otel.Handle(errBridgeNotSupported) + }) continue } n := len(c.resourceKeyVals.keys) + 2 // resource attrs + scope name + scope version From a35a58740579efd73923e6cb30c637232e0d45c8 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 8 Dec 2025 19:37:51 +0000 Subject: [PATCH 3/6] test error is handled --- exporters/prometheus/exporter_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index c773583496a..07e5de4e094 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -798,6 +798,9 @@ func TestMultiScopes(t *testing.T) { } func TestBridgeScopeIgnored(t *testing.T) { + var handledError error + eh := otel.ErrorHandlerFunc(func(e error) { handledError = errors.Join(handledError, e) }) + otel.SetErrorHandler(eh) ctx := t.Context() registry := prometheus.NewRegistry() exporter, err := New( @@ -835,6 +838,8 @@ func TestBridgeScopeIgnored(t *testing.T) { err = testutil.GatherAndCompare(registry, file) require.NoError(t, err) + + require.ErrorIs(t, handledError, errBridgeNotSupported) } func TestDuplicateMetrics(t *testing.T) { From e423ee68f5dc5c461a3068eb1ed0afced2fac864 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 8 Dec 2025 19:40:57 +0000 Subject: [PATCH 4/6] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecbe0582c48..3ec03ee98eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Changed + +- `Exporter` in `go.opentelemetry.io/otel/exporter/prometheus` ignores metrics with the scope `go.opentelemetry.io/contrib/bridges/prometheus`. + This prevents scrape failures when the Prometheus exporter is misconfigured to get data from the Prometheus bridge. (#7688) + From 831dd2743f9a7ea6200ee7f4dad81ee0bf31d5f8 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 8 Dec 2025 20:15:00 +0000 Subject: [PATCH 5/6] spelling --- exporters/prometheus/errors.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exporters/prometheus/errors.go b/exporters/prometheus/errors.go index 9b6e5c42d4e..7206acabfa5 100644 --- a/exporters/prometheus/errors.go +++ b/exporters/prometheus/errors.go @@ -10,5 +10,7 @@ var ( errInvalidMetricType = errors.New("invalid metric type") errInvalidMetric = errors.New("invalid metric") errEHScaleBelowMin = errors.New("exponential histogram scale below minimum supported") - errBridgeNotSupported = errors.New("metrics from the Prometheus Bridge are not supproted in the Prometheus exporter, and will be skipped") + errBridgeNotSupported = errors.New( + "metrics from the prometheus bridge are not supported in the prometheus exporter, and will be dropped", + ) ) From 4c4e0785ad33fd002abffd700207adcebc2455a7 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 8 Dec 2025 22:06:20 +0000 Subject: [PATCH 6/6] document that the exporter does not support the bridge --- exporters/prometheus/doc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exporters/prometheus/doc.go b/exporters/prometheus/doc.go index e9b77869ea5..2f49f375b5b 100644 --- a/exporters/prometheus/doc.go +++ b/exporters/prometheus/doc.go @@ -4,4 +4,8 @@ // Package prometheus provides a Prometheus Exporter that converts // OTLP metrics into the Prometheus exposition format and implements // prometheus.Collector to provide a handler for these metrics. +// +// The Prometheus exporter ignores metrics from the Prometheus bridge. To +// export these metrics, simply register them directly with the Prometheus +// Handler. package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"