001package io.prometheus.metrics.config; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.nio.file.Files; 006import java.nio.file.Paths; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.Map; 010import java.util.Properties; 011import java.util.Set; 012import java.util.regex.Matcher; 013import java.util.regex.Pattern; 014 015/** 016 * The Properties Loader is early stages. 017 * <p> 018 * It would be great to implement a subset of 019 * <a href="https://docs.spring.io/spring-boot/docs/3.1.x/reference/html/features.html#features.external-config">Spring Boot's Externalized Configuration</a>, 020 * like support for YAML, Properties, and env vars, or support for Spring's naming conventions for properties. 021 */ 022public class PrometheusPropertiesLoader { 023 024 /** 025 * See {@link PrometheusProperties#get()}. 026 */ 027 public static PrometheusProperties load() throws PrometheusPropertiesException { 028 return load(new Properties()); 029 } 030 031 public static PrometheusProperties load(Map<Object, Object> externalProperties) throws PrometheusPropertiesException { 032 Map<Object, Object> properties = loadProperties(externalProperties); 033 Map<String, MetricsProperties> metricsConfigs = loadMetricsConfigs(properties); 034 MetricsProperties defaultMetricsProperties = MetricsProperties.load("io.prometheus.metrics", properties); 035 ExemplarsProperties exemplarConfig = ExemplarsProperties.load("io.prometheus.exemplars", properties); 036 ExporterProperties exporterProperties = ExporterProperties.load("io.prometheus.exporter", properties); 037 ExporterFilterProperties exporterFilterProperties = ExporterFilterProperties.load("io.prometheus.exporter.filter", properties); 038 ExporterHttpServerProperties exporterHttpServerProperties = ExporterHttpServerProperties.load("io.prometheus.exporter.httpServer", properties); 039 ExporterPushgatewayProperties exporterPushgatewayProperties = ExporterPushgatewayProperties.load("io.prometheus.exporter.pushgateway", properties); 040 ExporterOpenTelemetryProperties exporterOpenTelemetryProperties = ExporterOpenTelemetryProperties.load("io.prometheus.exporter.opentelemetry", properties); 041 validateAllPropertiesProcessed(properties); 042 return new PrometheusProperties(defaultMetricsProperties, metricsConfigs, exemplarConfig, exporterProperties, exporterFilterProperties, exporterHttpServerProperties, exporterPushgatewayProperties, exporterOpenTelemetryProperties); 043 } 044 045 // This will remove entries from properties when they are processed. 046 private static Map<String, MetricsProperties> loadMetricsConfigs(Map<Object, Object> properties) { 047 Map<String, MetricsProperties> result = new HashMap<>(); 048 // Note that the metric name in the properties file must be as exposed in the Prometheus exposition formats, 049 // i.e. all dots replaced with underscores. 050 Pattern pattern = Pattern.compile("io\\.prometheus\\.metrics\\.([^.]+)\\."); 051 // Create a copy of the keySet() for iterating. We cannot iterate directly over keySet() 052 // because entries are removed when MetricsConfig.load(...) is called. 053 Set<String> propertyNames = new HashSet<>(); 054 for (Object key : properties.keySet()) { 055 propertyNames.add(key.toString()); 056 } 057 for (String propertyName : propertyNames) { 058 Matcher matcher = pattern.matcher(propertyName); 059 if (matcher.find()) { 060 String metricName = matcher.group(1).replace(".", "_"); 061 if (!result.containsKey(metricName)) { 062 result.put(metricName, MetricsProperties.load("io.prometheus.metrics." + metricName, properties)); 063 } 064 } 065 } 066 return result; 067 } 068 069 // If there are properties left starting with io.prometheus it's likely a typo, 070 // because we didn't use that property. 071 // Throw a config error to let the user know that this property doesn't exist. 072 private static void validateAllPropertiesProcessed(Map<Object, Object> properties) { 073 for (Object key : properties.keySet()) { 074 if (key.toString().startsWith("io.prometheus")) { 075 throw new PrometheusPropertiesException(key + ": Unknown property"); 076 } 077 } 078 } 079 080 private static Map<Object, Object> loadProperties(Map<Object, Object> externalProperties) { 081 Map<Object, Object> properties = new HashMap<>(); 082 properties.putAll(loadPropertiesFromClasspath()); 083 properties.putAll(loadPropertiesFromFile()); // overriding the entries from the classpath file 084 properties.putAll(System.getProperties()); // overriding the entries from the properties file 085 properties.putAll(externalProperties); // overriding all the entries above 086 // TODO: Add environment variables like EXEMPLARS_ENABLED. 087 return properties; 088 } 089 090 private static Properties loadPropertiesFromClasspath() { 091 Properties properties = new Properties(); 092 try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("prometheus.properties")) { 093 properties.load(stream); 094 } catch (Exception ignored) { 095 } 096 return properties; 097 } 098 099 private static Properties loadPropertiesFromFile() throws PrometheusPropertiesException { 100 Properties properties = new Properties(); 101 String path = System.getProperty("prometheus.config"); 102 if (System.getenv("PROMETHEUS_CONFIG") != null) { 103 path = System.getenv("PROMETHEUS_CONFIG"); 104 } 105 if (path != null) { 106 try (InputStream stream = Files.newInputStream(Paths.get(path))) { 107 properties.load(stream); 108 } catch (IOException e) { 109 throw new PrometheusPropertiesException("Failed to read Prometheus properties from " + path + ": " + e.getMessage(), e); 110 } 111 } 112 return properties; 113 } 114}