diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java index 04bbfe4a53..26334a6ba4 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java @@ -210,10 +210,18 @@ private static Map loadPropertiesFromEnvironment() { *

For example: exemplarsEnabled → exemplars_enabled exemplars_enabled → exemplars_enabled * (unchanged) * + *

In metric-specific keys like {@code io.prometheus.metrics..} only the + * property name is normalized, because metric names are case-sensitive. + * * @param key the property key * @return the normalized property key */ static String normalizePropertyKey(String key) { + String metricsPrefix = "io.prometheus.metrics."; + int lastDot = key.lastIndexOf('.'); + if (key.startsWith(metricsPrefix) && lastDot >= metricsPrefix.length()) { + return key.substring(0, lastDot + 1) + normalizePropertyKey(key.substring(lastDot + 1)); + } // Insert underscores before uppercase letters to convert camelCase to snake_case String withUnderscores = key.replaceAll("([a-z])([A-Z])", "$1_$2"); return withUnderscores.toLowerCase(java.util.Locale.ROOT); diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java index 8f910ccebd..f7b3f9505e 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java @@ -56,6 +56,22 @@ void externalPropertiesShouldOverridePropertiesFile() { .isFalse(); } + @Test + void metricSpecificPropertiesShouldKeepMetricNameCase() { + Properties properties = new Properties(); + properties.setProperty( + "io.prometheus.metrics.myLatency.histogram_classic_upper_bounds", ".005, .01, .015"); + properties.setProperty("io.prometheus.metrics.HTTP_duration.histogramNativeOnly", "true"); + + PrometheusProperties prometheusProperties = PrometheusPropertiesLoader.load(properties); + assertThat( + prometheusProperties.getMetricProperties("myLatency").getHistogramClassicUpperBounds()) + .containsExactly(0.005, 0.01, 0.015); + assertThat(prometheusProperties.getMetricProperties("HTTP_duration").getHistogramNativeOnly()) + .isTrue(); + assertThat(prometheusProperties.getMetricProperties("my_latency")).isNull(); + } + @Test void environmentVariablesShouldConfigureMetrics() { // Simulate environment variables as they would be loaded @@ -160,6 +176,10 @@ void normalizePropertyKeyShouldConvertCamelCaseToSnakeCase() { PrometheusPropertiesLoader.normalizePropertyKey( "io.prometheus.exporter.openTelemetry.serviceName")) .isEqualTo("io.prometheus.exporter.open_telemetry.service_name"); + assertThat( + PrometheusPropertiesLoader.normalizePropertyKey( + "io.prometheus.metrics.myLatency.histogramNativeOnly")) + .isEqualTo("io.prometheus.metrics.myLatency.histogram_native_only"); // Test that snake_case keys remain unchanged assertThat(