Skip to content

Commit e319d3b

Browse files
fix(config): keep metric name case in metric-specific properties (#2588)
## Problem Since 1.5.0, metric-specific properties are ignored when the metric name has an uppercase letter. With ```properties io.prometheus.metrics.myLatency.histogram_classic_upper_bounds=.005, .01, .015 ``` a histogram named `myLatency` keeps the default buckets. `normalizePropertyKey()`, added in #1828 to accept camelCase property names, lowercases the whole key including the metric name. The settings are stored under `my_latency`, while `StatefulMetric` looks them up as `myLatency` and gets `null`. There is no "Unknown property" error because the key was consumed. In 1.4.3 the same config (with `histogramClassicUpperBounds`) applies to `myLatency`; I ran both versions to check. An automated review comment on #1828 pointed this out, but metric names are still lowercased on main. ## Fix In `io.prometheus.metrics.<metric>.<property>` keys only the property name is normalized, so `io.prometheus.metrics.myLatency.histogramNativeOnly` becomes `io.prometheus.metrics.myLatency.histogram_native_only`. Default metric keys and exporter keys are normalized as before, and environment variables are not affected. A key written with uppercase letters for a lowercase metric, like `io.prometheus.metrics.My_metric.histogram_native_only` for `my_metric`, no longer applies. That matches 1.4.x. ## How tested New `PrometheusPropertiesLoaderTest.metricSpecificPropertiesShouldKeepMetricNameCase` fails on main with a `NullPointerException` because `getMetricProperties("myLatency")` returns `null`, and passes with this change. I also added a case to `normalizePropertyKeyShouldConvertCamelCaseToSnakeCase`. `prometheus-metrics-config` (62 tests, coverage check) and `prometheus-metrics-core` (178 tests) pass on JDK 25. google-java-format 1.37.0 and checkstyle are clean on the changed files. I did not run `mise run lint` or the exporter modules. @zeitlinger this changes the key normalization from #1828. Signed-off-by: Tanishq Choudhary <tanishqchoudhary007@gmail.com>
1 parent b347b8d commit e319d3b

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

‎prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,18 @@ private static Map<Object, Object> loadPropertiesFromEnvironment() {
210210
* <p>For example: exemplarsEnabled → exemplars_enabled exemplars_enabled → exemplars_enabled
211211
* (unchanged)
212212
*
213+
* <p>In metric-specific keys like {@code io.prometheus.metrics.<metric>.<property>} only the
214+
* property name is normalized, because metric names are case-sensitive.
215+
*
213216
* @param key the property key
214217
* @return the normalized property key
215218
*/
216219
static String normalizePropertyKey(String key) {
220+
String metricsPrefix = "io.prometheus.metrics.";
221+
int lastDot = key.lastIndexOf('.');
222+
if (key.startsWith(metricsPrefix) && lastDot >= metricsPrefix.length()) {
223+
return key.substring(0, lastDot + 1) + normalizePropertyKey(key.substring(lastDot + 1));
224+
}
217225
// Insert underscores before uppercase letters to convert camelCase to snake_case
218226
String withUnderscores = key.replaceAll("([a-z])([A-Z])", "$1_$2");
219227
return withUnderscores.toLowerCase(java.util.Locale.ROOT);

‎prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ void externalPropertiesShouldOverridePropertiesFile() {
5656
.isFalse();
5757
}
5858

59+
@Test
60+
void metricSpecificPropertiesShouldKeepMetricNameCase() {
61+
Properties properties = new Properties();
62+
properties.setProperty(
63+
"io.prometheus.metrics.myLatency.histogram_classic_upper_bounds", ".005, .01, .015");
64+
properties.setProperty("io.prometheus.metrics.HTTP_duration.histogramNativeOnly", "true");
65+
66+
PrometheusProperties prometheusProperties = PrometheusPropertiesLoader.load(properties);
67+
assertThat(
68+
prometheusProperties.getMetricProperties("myLatency").getHistogramClassicUpperBounds())
69+
.containsExactly(0.005, 0.01, 0.015);
70+
assertThat(prometheusProperties.getMetricProperties("HTTP_duration").getHistogramNativeOnly())
71+
.isTrue();
72+
assertThat(prometheusProperties.getMetricProperties("my_latency")).isNull();
73+
}
74+
5975
@Test
6076
void environmentVariablesShouldConfigureMetrics() {
6177
// Simulate environment variables as they would be loaded
@@ -160,6 +176,10 @@ void normalizePropertyKeyShouldConvertCamelCaseToSnakeCase() {
160176
PrometheusPropertiesLoader.normalizePropertyKey(
161177
"io.prometheus.exporter.openTelemetry.serviceName"))
162178
.isEqualTo("io.prometheus.exporter.open_telemetry.service_name");
179+
assertThat(
180+
PrometheusPropertiesLoader.normalizePropertyKey(
181+
"io.prometheus.metrics.myLatency.histogramNativeOnly"))
182+
.isEqualTo("io.prometheus.metrics.myLatency.histogram_native_only");
163183

164184
// Test that snake_case keys remain unchanged
165185
assertThat(

0 commit comments

Comments
 (0)