Skip to content

Commit de5cf18

Browse files
committed
fix env var properties
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent 540f782 commit de5cf18

5 files changed

Lines changed: 154 additions & 26 deletions

File tree

prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,27 @@ public class MetricsProperties {
2828
private static final String SUMMARY_MAX_AGE_SECONDS = "summary_max_age_seconds";
2929
private static final String SUMMARY_NUMBER_OF_AGE_BUCKETS = "summary_number_of_age_buckets";
3030

31+
/**
32+
* All known property suffixes that can be configured for metrics.
33+
*
34+
* <p>This list is used to parse metric-specific configuration keys from environment variables.
35+
*/
36+
static final String[] PROPERTY_SUFFIXES = {
37+
EXEMPLARS_ENABLED,
38+
HISTOGRAM_NATIVE_ONLY,
39+
HISTOGRAM_CLASSIC_ONLY,
40+
HISTOGRAM_CLASSIC_UPPER_BOUNDS,
41+
HISTOGRAM_NATIVE_INITIAL_SCHEMA,
42+
HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD,
43+
HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD,
44+
HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS,
45+
HISTOGRAM_NATIVE_RESET_DURATION_SECONDS,
46+
SUMMARY_QUANTILES,
47+
SUMMARY_QUANTILE_ERRORS,
48+
SUMMARY_MAX_AGE_SECONDS,
49+
SUMMARY_NUMBER_OF_AGE_BUCKETS
50+
};
51+
3152
@Nullable private final Boolean exemplarsEnabled;
3253
@Nullable private final Boolean histogramNativeOnly;
3354
@Nullable private final Boolean histogramClassicOnly;

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

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import java.util.Map;
99
import java.util.Properties;
1010
import java.util.Set;
11-
import java.util.regex.Matcher;
12-
import java.util.regex.Pattern;
1311

1412
/**
1513
* The Properties Loader is early stages.
@@ -55,25 +53,47 @@ public static PrometheusProperties load(Map<Object, Object> externalProperties)
5553
}
5654

5755
// This will remove entries from propertySource when they are processed.
58-
private static Map<String, MetricsProperties> loadMetricsConfigs(PropertySource propertySource) {
56+
static Map<String, MetricsProperties> loadMetricsConfigs(PropertySource propertySource) {
5957
Map<String, MetricsProperties> result = new HashMap<>();
6058
// Note that the metric name in the properties file must be as exposed in the Prometheus
6159
// exposition formats,
6260
// i.e. all dots replaced with underscores.
63-
Pattern pattern = Pattern.compile("io\\.prometheus\\.metrics\\.([^.]+)\\.");
61+
6462
// Get a snapshot of all keys for pattern matching. Entries will be removed
6563
// when MetricsProperties.load(...) is called.
6664
Set<String> propertyNames = propertySource.getAllKeys();
6765
for (String propertyName : propertyNames) {
68-
Matcher matcher = pattern.matcher(propertyName);
69-
if (matcher.find()) {
70-
String metricName = matcher.group(1).replace(".", "_");
71-
if (!result.containsKey(metricName)) {
72-
result.put(
73-
metricName,
74-
MetricsProperties.load("io.prometheus.metrics." + metricName, propertySource));
66+
String metricName = null;
67+
68+
if (propertyName.startsWith("io.prometheus.metrics.")) {
69+
// Dot-separated format (from regular properties, system properties, or files)
70+
String remainder = propertyName.substring("io.prometheus.metrics.".length());
71+
// Try to match against known property suffixes
72+
for (String suffix : MetricsProperties.PROPERTY_SUFFIXES) {
73+
if (remainder.endsWith("." + suffix)) {
74+
// Metric name in dot format, convert dots to underscores for exposition format
75+
metricName =
76+
remainder.substring(0, remainder.length() - suffix.length() - 1).replace(".", "_");
77+
break;
78+
}
79+
}
80+
} else if (propertyName.startsWith("io_prometheus_metrics_")) {
81+
// Underscore-separated format (from environment variables)
82+
String remainder = propertyName.substring("io_prometheus_metrics_".length());
83+
// Try to match against known property suffixes
84+
for (String suffix : MetricsProperties.PROPERTY_SUFFIXES) {
85+
if (remainder.endsWith("_" + suffix)) {
86+
metricName = remainder.substring(0, remainder.length() - suffix.length() - 1);
87+
break;
88+
}
7589
}
7690
}
91+
92+
if (metricName != null && !result.containsKey(metricName)) {
93+
result.put(
94+
metricName,
95+
MetricsProperties.load("io.prometheus.metrics." + metricName, propertySource));
96+
}
7797
}
7898
return result;
7999
}

prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PropertySource.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class PropertySource {
3232
*
3333
* @param externalProperties properties passed explicitly (e.g., from application code)
3434
* @param envVarProperties properties from environment variables (keys in env var format with
35-
* underscores)
35+
* underscores, lowercase)
3636
* @param regularProperties properties from system properties, files, and classpath (keys
3737
* normalized to snake_case)
3838
*/
@@ -117,23 +117,23 @@ String getProperty(String prefix, String propertyName) {
117117
/**
118118
* Returns all keys from all three property sources.
119119
*
120-
* <p>For environment variable keys, transforms them from underscore format to dot format (e.g.,
121-
* "io_prometheus_metrics_exemplars_enabled" becomes "io.prometheus.metrics.exemplars_enabled").
120+
* <p>Keys are returned in the format they are stored in each source: external and regular
121+
* properties typically use dot-separated keys, while environment variables are exposed in their
122+
* underscore form (e.g., "io_prometheus_metrics_exemplars_enabled").
122123
*
123124
* <p>This is used for pattern matching to find metric-specific configurations.
124125
*
125-
* @return a set of all property keys in normalized dot format
126+
* @return a set of all property keys
126127
*/
127128
Set<String> getAllKeys() {
128129
Set<String> allKeys = new HashSet<>();
129130
for (Object key : externalProperties.keySet()) {
130131
allKeys.add(key.toString());
131132
}
132-
// Transform env var keys from underscore to dot format
133+
// Include env var keys as stored (underscore-separated, lowercase)
133134
for (Object key : envVarProperties.keySet()) {
134135
String envKey = key.toString();
135-
String normalizedKey = envKey.replace("_", ".");
136-
allKeys.add(normalizedKey);
136+
allKeys.add(envKey);
137137
}
138138
for (Object key : regularProperties.keySet()) {
139139
allKeys.add(key.toString());

prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package io.prometheus.metrics.config;
22

3-
import org.junit.jupiter.api.Test;
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
45

56
import java.util.HashMap;
67
import java.util.Map;
7-
8-
import static org.assertj.core.api.Assertions.assertThat;
9-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
8+
import org.junit.jupiter.api.Test;
109

1110
class ExemplarsPropertiesTest {
1211

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

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
55

6+
import java.util.HashMap;
7+
import java.util.Map;
68
import java.util.Properties;
79
import org.junit.jupiter.api.Test;
810
import org.junitpioneer.jupiter.SetSystemProperty;
@@ -28,10 +30,7 @@ void propertiesShouldBeLoadedFromPropertiesFile() {
2830
@SetSystemProperty(key = "prometheus.config", value = "nonexistent.properties")
2931
void cantLoadPropertiesFile() {
3032
assertThatExceptionOfType(PrometheusPropertiesException.class)
31-
.isThrownBy(
32-
() -> {
33-
PrometheusPropertiesLoader.load(new Properties());
34-
})
33+
.isThrownBy(() -> PrometheusPropertiesLoader.load(new Properties()))
3534
.withMessage(
3635
"Failed to read Prometheus properties from nonexistent.properties:"
3736
+ " nonexistent.properties");
@@ -57,4 +56,93 @@ void externalPropertiesShouldOverridePropertiesFile() {
5756
assertThat(prometheusProperties.getExporterProperties().getExemplarsOnAllMetricTypes())
5857
.isFalse();
5958
}
59+
60+
@Test
61+
void environmentVariablesShouldConfigureMetrics() {
62+
// Simulate environment variables as they would be loaded
63+
Map<Object, Object> envVarProperties = new HashMap<>();
64+
envVarProperties.put(
65+
"io_prometheus_metrics_http_duration_seconds_histogram_classic_upper_bounds",
66+
".001, .005, .01");
67+
envVarProperties.put("io_prometheus_metrics_exemplars_enabled", "false");
68+
69+
PropertySource propertySource =
70+
new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>());
71+
Map<String, MetricsProperties> metricsConfigs =
72+
PrometheusPropertiesLoader.loadMetricsConfigs(propertySource);
73+
74+
assertThat(metricsConfigs.get("http_duration_seconds").getHistogramClassicUpperBounds())
75+
.hasSize(3)
76+
.containsExactly(0.001, 0.005, 0.01);
77+
78+
MetricsProperties defaultMetrics =
79+
MetricsProperties.load("io.prometheus.metrics", propertySource);
80+
assertThat(defaultMetrics.getExemplarsEnabled()).isFalse();
81+
}
82+
83+
@Test
84+
void environmentVariablesShouldHandleSnakeCaseMetricNames() {
85+
// Simulate environment variable for metric with snake_case name
86+
Map<Object, Object> envVarProperties = new HashMap<>();
87+
envVarProperties.put("io_prometheus_metrics_http_server_histogram_native_only", "true");
88+
89+
PropertySource propertySource =
90+
new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>());
91+
Map<String, MetricsProperties> metricsConfigs =
92+
PrometheusPropertiesLoader.loadMetricsConfigs(propertySource);
93+
94+
assertThat(metricsConfigs.get("http_server").getHistogramNativeOnly()).isTrue();
95+
}
96+
97+
@Test
98+
void environmentVariablesShouldHandleMultipleSnakeCaseSegments() {
99+
// Simulate environment variable for metric with multiple snake_case segments
100+
Map<Object, Object> envVarProperties = new HashMap<>();
101+
envVarProperties.put("io_prometheus_metrics_my_custom_metric_histogram_native_only", "true");
102+
103+
PropertySource propertySource =
104+
new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>());
105+
Map<String, MetricsProperties> metricsConfigs =
106+
PrometheusPropertiesLoader.loadMetricsConfigs(propertySource);
107+
108+
assertThat(metricsConfigs.get("my_custom_metric").getHistogramNativeOnly()).isTrue();
109+
}
110+
111+
@Test
112+
void environmentVariablesShouldHandleMetricNamesContainingPropertyKeywords() {
113+
// Metric names can contain words like "summary" or "histogram"
114+
// This should not confuse the parser
115+
Map<Object, Object> envVarProperties = new HashMap<>();
116+
envVarProperties.put("io_prometheus_metrics_my_summary_metric_histogram_native_only", "true");
117+
envVarProperties.put(
118+
"io_prometheus_metrics_histogram_processor_summary_quantiles", "0.5, 0.95");
119+
120+
PropertySource propertySource =
121+
new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>());
122+
Map<String, MetricsProperties> metricsConfigs =
123+
PrometheusPropertiesLoader.loadMetricsConfigs(propertySource);
124+
125+
assertThat(metricsConfigs.get("my_summary_metric").getHistogramNativeOnly()).isTrue();
126+
assertThat(metricsConfigs.get("histogram_processor").getSummaryQuantiles())
127+
.containsExactly(0.5, 0.95);
128+
}
129+
130+
@Test
131+
void regularPropertiesShouldHandleComplexMetricNames() {
132+
// Test that suffix-based matching works correctly for regular properties
133+
// Metric names already use underscores (exposition format)
134+
Properties properties = new Properties();
135+
properties.setProperty(
136+
"io.prometheus.metrics.http_server_requests_total.histogram_native_only", "true");
137+
properties.setProperty(
138+
"io.prometheus.metrics.my_app_custom_metric.summary_quantiles", "0.5, 0.99");
139+
140+
PropertySource propertySource = new PropertySource(properties);
141+
Map<String, MetricsProperties> metricsConfigs =
142+
PrometheusPropertiesLoader.loadMetricsConfigs(propertySource);
143+
144+
assertThat(metricsConfigs.get("http_server_requests_total").getHistogramNativeOnly()).isTrue();
145+
assertThat(metricsConfigs.get("my_app_custom_metric").getSummaryQuantiles())
146+
.containsExactly(0.5, 0.99);
147+
}
60148
}

0 commit comments

Comments
 (0)