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