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 Map<Object, Object> properties = loadProperties(); 029 Map<String, MetricsProperties> metricsConfigs = loadMetricsConfigs(properties); 030 MetricsProperties defaultMetricsProperties = MetricsProperties.load("io.prometheus.metrics", properties); 031 ExemplarsProperties exemplarConfig = ExemplarsProperties.load("io.prometheus.exemplars", properties); 032 ExporterProperties exporterProperties = ExporterProperties.load("io.prometheus.exporter", properties); 033 ExporterFilterProperties exporterFilterProperties = ExporterFilterProperties.load("io.prometheus.exporter.filter", properties); 034 ExporterHttpServerProperties exporterHttpServerProperties = ExporterHttpServerProperties.load("io.prometheus.exporter.httpServer", properties); 035 validateAllPropertiesProcessed(properties); 036 return new PrometheusProperties(defaultMetricsProperties, metricsConfigs, exemplarConfig, exporterProperties, exporterFilterProperties, exporterHttpServerProperties); 037 } 038 039 // This will remove entries from properties when they are processed. 040 private static Map<String, MetricsProperties> loadMetricsConfigs(Map<Object, Object> properties) { 041 Map<String, MetricsProperties> result = new HashMap<>(); 042 // Note that the metric name in the properties file must be as exposed in the Prometheus exposition formats, 043 // i.e. all dots replaced with underscores. 044 Pattern pattern = Pattern.compile("io\\.prometheus\\.metrics\\.([^.]+)\\."); 045 // Create a copy of the keySet() for iterating. We cannot iterate directly over keySet() 046 // because entries are removed when MetricsConfig.load(...) is called. 047 Set<String> propertyNames = new HashSet<>(); 048 for (Object key : properties.keySet()) { 049 propertyNames.add(key.toString()); 050 } 051 for (String propertyName : propertyNames) { 052 Matcher matcher = pattern.matcher(propertyName); 053 if (matcher.find()) { 054 String metricName = matcher.group(1).replace(".", "_"); 055 if (!result.containsKey(metricName)) { 056 result.put(metricName, MetricsProperties.load("io.prometheus.metrics." + metricName, properties)); 057 } 058 } 059 } 060 return result; 061 } 062 063 // If there are properties left starting with io.prometheus it's likely a typo, 064 // because we didn't use that property. 065 // Throw a config error to let the user know that this property doesn't exist. 066 private static void validateAllPropertiesProcessed(Map<Object, Object> properties) { 067 for (Object key : properties.keySet()) { 068 if (key.toString().startsWith("io.prometheus")) { 069 throw new PrometheusPropertiesException(key + ": Unknown property"); 070 } 071 } 072 } 073 074 private static Map<Object, Object> loadProperties() { 075 Map<Object, Object> properties = new HashMap<>(); 076 properties.putAll(loadPropertiesFromClasspath()); 077 properties.putAll(loadPropertiesFromFile()); // overriding the entries from the classpath file 078 properties.putAll(System.getProperties()); // overriding the entries from the properties file 079 // TODO: Add environment variables like EXEMPLARS_ENABLED. 080 return properties; 081 } 082 083 private static Properties loadPropertiesFromClasspath() { 084 Properties properties = new Properties(); 085 try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("prometheus.properties")) { 086 properties.load(stream); 087 } catch (Exception ignored) { 088 } 089 return properties; 090 } 091 092 private static Properties loadPropertiesFromFile() throws PrometheusPropertiesException { 093 Properties properties = new Properties(); 094 String path = System.getProperty("prometheus.config"); 095 if (System.getenv("PROMETHEUS_CONFIG") != null) { 096 path = System.getenv("PROMETHEUS_CONFIG"); 097 } 098 if (path != null) { 099 try (InputStream stream = Files.newInputStream(Paths.get(path))) { 100 properties.load(stream); 101 } catch (IOException e) { 102 throw new PrometheusPropertiesException("Failed to read Prometheus properties from " + path + ": " + e.getMessage(), e); 103 } 104 } 105 return properties; 106 } 107}