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) + 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" diff --git a/exporters/prometheus/errors.go b/exporters/prometheus/errors.go index f2076de761e..7206acabfa5 100644 --- a/exporters/prometheus/errors.go +++ b/exporters/prometheus/errors.go @@ -7,7 +7,10 @@ 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 supported in the prometheus exporter, and will be dropped", + ) ) diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index d73786b83ed..0f2259af381 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{ @@ -96,6 +101,8 @@ type collector struct { unitNamer otlptranslator.UnitNamer inst *observ.Instrumentation + + bridgeErrorOnce sync.Once } // New returns a Prometheus Exporter. @@ -229,6 +236,12 @@ 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 kv := keyVals{ keys: make([]string, 0, n), diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index 2c28bb1dbda..07e5de4e094 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -797,6 +797,51 @@ func TestMultiScopes(t *testing.T) { require.NoError(t, err) } +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( + 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) + + require.ErrorIs(t, handledError, errBridgeNotSupported) +} + 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