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