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 =