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