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