From 05178862569de9a5cd24b4dd1066ae6cf891cccd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 17 Mar 2026 07:47:23 +0000 Subject: [PATCH] feat: Add OpenMetrics2 enabled flag for explicit OM2 writer activation The OM2 writer selection previously activated when any feature flag was set, but there was no way to just enable OM2 without opting into a specific feature. This adds an explicit `enabled` gate (io.prometheus.openmetrics2.enabled) as the single control for OM2 writer selection. Feature flags alone no longer activate OM2. The programmatic `enableOpenMetrics2()` configurator sets enabled=true implicitly, matching its name. Signed-off-by: Gregor Zeitlinger --- .../config/OpenMetrics2Properties.java | 25 ++++++++++- .../metrics/config/PrometheusProperties.java | 3 +- .../config/OpenMetrics2PropertiesTest.java | 12 +++++ .../expositionformats/ExpositionFormats.java | 6 +-- .../ExpositionFormatsTest.java | 44 ++++--------------- 5 files changed, 47 insertions(+), 43 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/OpenMetrics2Properties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/OpenMetrics2Properties.java index a92536036..be1d13279 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/OpenMetrics2Properties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/OpenMetrics2Properties.java @@ -9,27 +9,39 @@ public class OpenMetrics2Properties { private static final String PREFIX = "io.prometheus.openmetrics2"; + private static final String ENABLED = "enabled"; private static final String CONTENT_NEGOTIATION = "content_negotiation"; private static final String COMPOSITE_VALUES = "composite_values"; private static final String EXEMPLAR_COMPLIANCE = "exemplar_compliance"; private static final String NATIVE_HISTOGRAMS = "native_histograms"; + @Nullable private final Boolean enabled; @Nullable private final Boolean contentNegotiation; @Nullable private final Boolean compositeValues; @Nullable private final Boolean exemplarCompliance; @Nullable private final Boolean nativeHistograms; private OpenMetrics2Properties( + @Nullable Boolean enabled, @Nullable Boolean contentNegotiation, @Nullable Boolean compositeValues, @Nullable Boolean exemplarCompliance, @Nullable Boolean nativeHistograms) { + this.enabled = enabled; this.contentNegotiation = contentNegotiation; this.compositeValues = compositeValues; this.exemplarCompliance = exemplarCompliance; this.nativeHistograms = nativeHistograms; } + /** + * Enable the OpenMetrics 2.0 text format writer. When {@code true}, the OM2 writer is used + * instead of OM1 for OpenMetrics responses. Default is {@code false}. + */ + public boolean getEnabled() { + return enabled != null && enabled; + } + /** Gate OM2 features behind content negotiation. Default is {@code false}. */ public boolean getContentNegotiation() { return contentNegotiation != null && contentNegotiation; @@ -56,12 +68,13 @@ public boolean getNativeHistograms() { */ static OpenMetrics2Properties load(PropertySource propertySource) throws PrometheusPropertiesException { + Boolean enabled = Util.loadBoolean(PREFIX, ENABLED, propertySource); Boolean contentNegotiation = Util.loadBoolean(PREFIX, CONTENT_NEGOTIATION, propertySource); Boolean compositeValues = Util.loadBoolean(PREFIX, COMPOSITE_VALUES, propertySource); Boolean exemplarCompliance = Util.loadBoolean(PREFIX, EXEMPLAR_COMPLIANCE, propertySource); Boolean nativeHistograms = Util.loadBoolean(PREFIX, NATIVE_HISTOGRAMS, propertySource); return new OpenMetrics2Properties( - contentNegotiation, compositeValues, exemplarCompliance, nativeHistograms); + enabled, contentNegotiation, compositeValues, exemplarCompliance, nativeHistograms); } public static Builder builder() { @@ -70,6 +83,7 @@ public static Builder builder() { public static class Builder { + @Nullable private Boolean enabled; @Nullable private Boolean contentNegotiation; @Nullable private Boolean compositeValues; @Nullable private Boolean exemplarCompliance; @@ -77,6 +91,12 @@ public static class Builder { private Builder() {} + /** See {@link #getEnabled()} */ + public Builder enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + /** See {@link #getContentNegotiation()} */ public Builder contentNegotiation(boolean contentNegotiation) { this.contentNegotiation = contentNegotiation; @@ -103,6 +123,7 @@ public Builder nativeHistograms(boolean nativeHistograms) { /** Enable all OpenMetrics 2.0 features */ public Builder enableAll() { + this.enabled = true; this.contentNegotiation = true; this.compositeValues = true; this.exemplarCompliance = true; @@ -112,7 +133,7 @@ public Builder enableAll() { public OpenMetrics2Properties build() { return new OpenMetrics2Properties( - contentNegotiation, compositeValues, exemplarCompliance, nativeHistograms); + enabled, contentNegotiation, compositeValues, exemplarCompliance, nativeHistograms); } } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index a9045d711..55e7d8dab 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -242,7 +242,8 @@ public Builder exporterOpenTelemetryProperties( } public Builder enableOpenMetrics2(Consumer configurator) { - OpenMetrics2Properties.Builder openMetrics2Builder = OpenMetrics2Properties.builder(); + OpenMetrics2Properties.Builder openMetrics2Builder = + OpenMetrics2Properties.builder().enabled(true); configurator.accept(openMetrics2Builder); this.openMetrics2Properties = openMetrics2Builder.build(); return this; diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java index c3a0b9fca..e7a273464 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java @@ -15,6 +15,8 @@ void load() { load( new HashMap<>( Map.of( + "io.prometheus.openmetrics2.enabled", + "true", "io.prometheus.openmetrics2.content_negotiation", "true", "io.prometheus.openmetrics2.composite_values", @@ -23,6 +25,7 @@ void load() { "true", "io.prometheus.openmetrics2.native_histograms", "true"))); + assertThat(properties.getEnabled()).isTrue(); assertThat(properties.getContentNegotiation()).isTrue(); assertThat(properties.getCompositeValues()).isTrue(); assertThat(properties.getExemplarCompliance()).isTrue(); @@ -31,6 +34,11 @@ void load() { @Test void loadInvalidValue() { + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy( + () -> load(new HashMap<>(Map.of("io.prometheus.openmetrics2.enabled", "invalid")))) + .withMessage( + "io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'. Found: invalid"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -79,11 +87,13 @@ private static OpenMetrics2Properties load(Map map) { void builder() { OpenMetrics2Properties properties = OpenMetrics2Properties.builder() + .enabled(true) .contentNegotiation(true) .compositeValues(false) .exemplarCompliance(true) .nativeHistograms(false) .build(); + assertThat(properties.getEnabled()).isTrue(); assertThat(properties.getContentNegotiation()).isTrue(); assertThat(properties.getCompositeValues()).isFalse(); assertThat(properties.getExemplarCompliance()).isTrue(); @@ -93,6 +103,7 @@ void builder() { @Test void builderEnableAll() { OpenMetrics2Properties properties = OpenMetrics2Properties.builder().enableAll().build(); + assertThat(properties.getEnabled()).isTrue(); assertThat(properties.getContentNegotiation()).isTrue(); assertThat(properties.getCompositeValues()).isTrue(); assertThat(properties.getExemplarCompliance()).isTrue(); @@ -102,6 +113,7 @@ void builderEnableAll() { @Test void defaultValues() { OpenMetrics2Properties properties = OpenMetrics2Properties.builder().build(); + assertThat(properties.getEnabled()).isFalse(); assertThat(properties.getContentNegotiation()).isFalse(); assertThat(properties.getCompositeValues()).isFalse(); assertThat(properties.getExemplarCompliance()).isFalse(); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java index ea2b294a2..a4a7088b8 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java @@ -76,11 +76,7 @@ public ExpositionFormatWriter findWriter(@Nullable String acceptHeader) { } private boolean isOpenMetrics2Enabled() { - OpenMetrics2Properties props = openMetrics2TextFormatWriter.getOpenMetrics2Properties(); - return props.getContentNegotiation() - || props.getCompositeValues() - || props.getExemplarCompliance() - || props.getNativeHistograms(); + return openMetrics2TextFormatWriter.getOpenMetrics2Properties().getEnabled(); } public PrometheusProtobufWriter getPrometheusProtobufWriter() { diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 35619042c..339c5dfa0 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -119,60 +119,36 @@ void testOM2DisabledByDefault() { } @Test - void testOM2EnabledWithContentNegotiation() { + void testOM2EnabledOnly() { PrometheusProperties props = PrometheusProperties.builder() - .openMetrics2Properties( - OpenMetrics2Properties.builder().contentNegotiation(true).build()) + .openMetrics2Properties(OpenMetrics2Properties.builder().enabled(true).build()) .build(); ExpositionFormats formats = ExpositionFormats.init(props); ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text"); - // When contentNegotiation is enabled, should return OM2 writer assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class); } @Test - void testOM2EnabledWithCompositeValues() { - PrometheusProperties props = - PrometheusProperties.builder() - .openMetrics2Properties(OpenMetrics2Properties.builder().compositeValues(true).build()) - .build(); - ExpositionFormats formats = ExpositionFormats.init(props); - ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text"); - assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class); - } - - @Test - void testOM2EnabledWithExemplarCompliance() { + void testOM2NotEnabledByFeatureFlagAlone() { + // Feature flags without enabled=true should not activate the OM2 writer PrometheusProperties props = PrometheusProperties.builder() .openMetrics2Properties( - OpenMetrics2Properties.builder().exemplarCompliance(true).build()) - .build(); - ExpositionFormats formats = ExpositionFormats.init(props); - ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text"); - // When exemplarCompliance is enabled, should return OM2 writer - assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class); - } - - @Test - void testOM2EnabledWithNativeHistograms() { - PrometheusProperties props = - PrometheusProperties.builder() - .openMetrics2Properties(OpenMetrics2Properties.builder().nativeHistograms(true).build()) + OpenMetrics2Properties.builder().contentNegotiation(true).build()) .build(); ExpositionFormats formats = ExpositionFormats.init(props); ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text"); - // When nativeHistograms is enabled, should return OM2 writer - assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class); + assertThat(writer).isInstanceOf(OpenMetricsTextFormatWriter.class); } @Test - void testOM2EnabledWithMultipleFlags() { + void testOM2EnabledWithFeatureFlags() { PrometheusProperties props = PrometheusProperties.builder() .openMetrics2Properties( OpenMetrics2Properties.builder() + .enabled(true) .contentNegotiation(true) .compositeValues(true) .nativeHistograms(true) @@ -180,7 +156,6 @@ void testOM2EnabledWithMultipleFlags() { .build(); ExpositionFormats formats = ExpositionFormats.init(props); ExpositionFormatWriter writer = formats.findWriter("application/openmetrics-text"); - // When multiple OM2 flags are enabled, should return OM2 writer assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class); } @@ -188,8 +163,7 @@ void testOM2EnabledWithMultipleFlags() { void testProtobufWriterTakesPrecedence() { PrometheusProperties props = PrometheusProperties.builder() - .openMetrics2Properties( - OpenMetrics2Properties.builder().contentNegotiation(true).build()) + .openMetrics2Properties(OpenMetrics2Properties.builder().enabled(true).build()) .build(); ExpositionFormats formats = ExpositionFormats.init(props); ExpositionFormatWriter writer =