001package io.prometheus.metrics.config;
002
003import java.util.HashMap;
004import java.util.Map;
005import java.util.function.Consumer;
006import javax.annotation.Nullable;
007
008/**
009 * The Prometheus Java client library can be configured at runtime (e.g. using a properties file).
010 *
011 * <p>This class represents the runtime configuration.
012 */
013public class PrometheusProperties {
014
015  private static final PrometheusProperties instance = PrometheusPropertiesLoader.load();
016
017  private final MetricsProperties defaultMetricsProperties;
018  private final MetricPropertiesMap metricProperties;
019  private final ExemplarsProperties exemplarProperties;
020  private final ExporterProperties exporterProperties;
021  private final ExporterFilterProperties exporterFilterProperties;
022  private final ExporterHttpServerProperties exporterHttpServerProperties;
023  private final ExporterOpenTelemetryProperties exporterOpenTelemetryProperties;
024  private final ExporterPushgatewayProperties exporterPushgatewayProperties;
025  private final OpenMetrics2Properties openMetrics2Properties;
026
027  /**
028   * Map that stores metric-specific properties keyed by metric name in exposition format
029   * (underscores instead of dots).
030   *
031   * <p>This wrapper makes it explicit that metric names are normalized to underscore format for
032   * storage, so that environment variables and properties with dots in metric names can be
033   * correctly looked up using normalized names.
034   */
035  static class MetricPropertiesMap {
036    private final Map<String, MetricsProperties> map = new HashMap<>();
037
038    void set(Map<String, MetricsProperties> properties) {
039      map.clear();
040      properties.forEach(this::put);
041    }
042
043    void put(String metricName, MetricsProperties properties) {
044      map.put(normalize(metricName), properties);
045    }
046
047    /**
048     * Get metric properties by metric name.
049     *
050     * <p>Accepts metric names in any format (with dots or underscores) and automatically converts
051     * them to the normalized underscore format used for storage.
052     *
053     * @param metricName the metric name (dots will be converted to underscores)
054     * @return the metric properties, or null if not configured
055     */
056    @Nullable
057    MetricsProperties get(String metricName) {
058      return map.get(normalize(metricName));
059    }
060
061    // copied from PrometheusNaming - but we can't reuse that class here because it's in a module
062    // that
063    // depends on PrometheusProperties, which would create a circular dependency.
064    private static String normalize(String name) {
065      StringBuilder escaped = new StringBuilder();
066
067      for (int i = 0; i < name.length(); ) {
068        int c = name.codePointAt(i);
069        if (isValidLegacyChar(c, i)) {
070          escaped.appendCodePoint(c);
071        } else {
072          escaped.append('_');
073        }
074        i += Character.charCount(c);
075      }
076      return escaped.toString();
077    }
078  }
079
080  private static boolean isValidLegacyChar(int c, int i) {
081    return (c >= 'a' && c <= 'z')
082        || (c >= 'A' && c <= 'Z')
083        || c == '_'
084        || c == ':'
085        || (c >= '0' && c <= '9' && i > 0);
086  }
087
088  /**
089   * Get the properties instance. When called for the first time, {@code get()} loads the properties
090   * from the following locations:
091   *
092   * <ul>
093   *   <li>{@code prometheus.properties} file found in the classpath.
094   *   <li>Properties file specified in the {@code PROMETHEUS_CONFIG} environment variable or the
095   *       {@code prometheus.config} system property.
096   *   <li>Individual properties from system properties.
097   * </ul>
098   */
099  public static PrometheusProperties get() throws PrometheusPropertiesException {
100    return instance;
101  }
102
103  public static Builder builder() {
104    return new Builder();
105  }
106
107  // Package-private constructor for PrometheusPropertiesLoader and Builder
108  PrometheusProperties(
109      MetricsProperties defaultMetricsProperties,
110      MetricPropertiesMap metricProperties,
111      ExemplarsProperties exemplarProperties,
112      ExporterProperties exporterProperties,
113      ExporterFilterProperties exporterFilterProperties,
114      ExporterHttpServerProperties httpServerConfig,
115      ExporterPushgatewayProperties pushgatewayProperties,
116      ExporterOpenTelemetryProperties otelConfig,
117      OpenMetrics2Properties openMetrics2Properties) {
118    this.defaultMetricsProperties = defaultMetricsProperties;
119    this.metricProperties = metricProperties;
120    this.exemplarProperties = exemplarProperties;
121    this.exporterProperties = exporterProperties;
122    this.exporterFilterProperties = exporterFilterProperties;
123    this.exporterHttpServerProperties = httpServerConfig;
124    this.exporterPushgatewayProperties = pushgatewayProperties;
125    this.exporterOpenTelemetryProperties = otelConfig;
126    this.openMetrics2Properties = openMetrics2Properties;
127  }
128
129  /**
130   * The default metric properties apply for metrics where {@link #getMetricProperties(String)} is
131   * {@code null}.
132   */
133  public MetricsProperties getDefaultMetricProperties() {
134    return defaultMetricsProperties;
135  }
136
137  /**
138   * Properties specific for one metric. Should be merged with {@link
139   * #getDefaultMetricProperties()}. May return {@code null} if no metric-specific properties are
140   * configured for a metric name.
141   *
142   * @param metricName the metric name (dots will be automatically converted to underscores to match
143   *     exposition format)
144   */
145  @Nullable
146  public MetricsProperties getMetricProperties(String metricName) {
147    return metricProperties.get(metricName);
148  }
149
150  public ExemplarsProperties getExemplarProperties() {
151    return exemplarProperties;
152  }
153
154  public ExporterProperties getExporterProperties() {
155    return exporterProperties;
156  }
157
158  public ExporterFilterProperties getExporterFilterProperties() {
159    return exporterFilterProperties;
160  }
161
162  public ExporterHttpServerProperties getExporterHttpServerProperties() {
163    return exporterHttpServerProperties;
164  }
165
166  public ExporterPushgatewayProperties getExporterPushgatewayProperties() {
167    return exporterPushgatewayProperties;
168  }
169
170  public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() {
171    return exporterOpenTelemetryProperties;
172  }
173
174  public OpenMetrics2Properties getOpenMetrics2Properties() {
175    return openMetrics2Properties;
176  }
177
178  public static class Builder {
179    private MetricsProperties defaultMetricsProperties = MetricsProperties.builder().build();
180    private final MetricPropertiesMap metricProperties = new MetricPropertiesMap();
181    private ExemplarsProperties exemplarProperties = ExemplarsProperties.builder().build();
182    private ExporterProperties exporterProperties = ExporterProperties.builder().build();
183    private ExporterFilterProperties exporterFilterProperties =
184        ExporterFilterProperties.builder().build();
185    private ExporterHttpServerProperties exporterHttpServerProperties =
186        ExporterHttpServerProperties.builder().build();
187    private ExporterPushgatewayProperties pushgatewayProperties =
188        ExporterPushgatewayProperties.builder().build();
189    private ExporterOpenTelemetryProperties otelConfig =
190        ExporterOpenTelemetryProperties.builder().build();
191    private OpenMetrics2Properties openMetrics2Properties =
192        OpenMetrics2Properties.builder().build();
193
194    private Builder() {}
195
196    public Builder defaultMetricsProperties(MetricsProperties defaultMetricsProperties) {
197      this.defaultMetricsProperties = defaultMetricsProperties;
198      return this;
199    }
200
201    public Builder metricProperties(Map<String, MetricsProperties> metricProperties) {
202      this.metricProperties.set(metricProperties);
203      return this;
204    }
205
206    /** Convenience for adding a single named MetricsProperties */
207    public Builder putMetricProperty(String name, MetricsProperties props) {
208      this.metricProperties.put(name, props);
209      return this;
210    }
211
212    public Builder exemplarProperties(ExemplarsProperties exemplarProperties) {
213      this.exemplarProperties = exemplarProperties;
214      return this;
215    }
216
217    public Builder exporterProperties(ExporterProperties exporterProperties) {
218      this.exporterProperties = exporterProperties;
219      return this;
220    }
221
222    public Builder exporterFilterProperties(ExporterFilterProperties exporterFilterProperties) {
223      this.exporterFilterProperties = exporterFilterProperties;
224      return this;
225    }
226
227    public Builder exporterHttpServerProperties(
228        ExporterHttpServerProperties exporterHttpServerProperties) {
229      this.exporterHttpServerProperties = exporterHttpServerProperties;
230      return this;
231    }
232
233    public Builder pushgatewayProperties(ExporterPushgatewayProperties pushgatewayProperties) {
234      this.pushgatewayProperties = pushgatewayProperties;
235      return this;
236    }
237
238    public Builder exporterOpenTelemetryProperties(
239        ExporterOpenTelemetryProperties exporterOpenTelemetryProperties) {
240      this.otelConfig = exporterOpenTelemetryProperties;
241      return this;
242    }
243
244    public Builder enableOpenMetrics2(Consumer<OpenMetrics2Properties.Builder> configurator) {
245      OpenMetrics2Properties.Builder openMetrics2Builder = OpenMetrics2Properties.builder();
246      configurator.accept(openMetrics2Builder);
247      this.openMetrics2Properties = openMetrics2Builder.build();
248      return this;
249    }
250
251    public Builder openMetrics2Properties(OpenMetrics2Properties openMetrics2Properties) {
252      this.openMetrics2Properties = openMetrics2Properties;
253      return this;
254    }
255
256    public PrometheusProperties build() {
257      return new PrometheusProperties(
258          defaultMetricsProperties,
259          metricProperties,
260          exemplarProperties,
261          exporterProperties,
262          exporterFilterProperties,
263          exporterHttpServerProperties,
264          pushgatewayProperties,
265          otelConfig,
266          openMetrics2Properties);
267    }
268  }
269}