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