001package io.prometheus.metrics.config;
002
003import java.util.HashMap;
004import java.util.Map;
005import javax.annotation.Nullable;
006
007/**
008 * Properties for configuring the OpenTelemetry exporter.
009 *
010 * <p>These properties can be configured via {@code prometheus.properties}, system properties, or
011 * programmatically.
012 *
013 * <p>All properties are prefixed with {@code io.prometheus.exporter.opentelemetry}.
014 *
015 * <p>Available properties:
016 *
017 * <ul>
018 *   <li>{@code protocol} - OTLP protocol: {@code "grpc"} or {@code "http/protobuf"}
019 *   <li>{@code endpoint} - OTLP endpoint URL
020 *   <li>{@code headers} - HTTP headers for outgoing requests
021 *   <li>{@code intervalSeconds} - Export interval in seconds
022 *   <li>{@code timeoutSeconds} - Request timeout in seconds
023 *   <li>{@code serviceName} - Service name resource attribute
024 *   <li>{@code serviceNamespace} - Service namespace resource attribute
025 *   <li>{@code serviceInstanceId} - Service instance ID resource attribute
026 *   <li>{@code serviceVersion} - Service version resource attribute
027 *   <li>{@code resourceAttributes} - Additional resource attributes
028 * </ul>
029 *
030 * @see <a
031 *     href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/">OpenTelemetry
032 *     SDK Environment Variables</a>
033 */
034public class ExporterOpenTelemetryProperties {
035
036  // See
037  // https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md
038  private static final String PROTOCOL = "protocol"; // otel.exporter.otlp.protocol
039  private static final String ENDPOINT = "endpoint"; // otel.exporter.otlp.endpoint
040  private static final String HEADERS = "headers"; // otel.exporter.otlp.headers
041  private static final String INTERVAL_SECONDS = "interval_seconds"; // otel.metric.export.interval
042  private static final String TIMEOUT_SECONDS = "timeout_seconds"; // otel.exporter.otlp.timeout
043  private static final String SERVICE_NAME = "service_name"; // otel.service.name
044  private static final String SERVICE_NAMESPACE = "service_namespace";
045  private static final String SERVICE_INSTANCE_ID = "service_instance_id";
046  private static final String SERVICE_VERSION = "service_version";
047  private static final String RESOURCE_ATTRIBUTES =
048      "resource_attributes"; // otel.resource.attributes
049  private static final String PREFIX = "io.prometheus.exporter.opentelemetry";
050
051  @Nullable private final String endpoint;
052  @Nullable private final String protocol;
053  private final Map<String, String> headers;
054  @Nullable private final String interval;
055  @Nullable private final String timeout;
056  @Nullable private final String serviceName;
057  @Nullable private final String serviceNamespace;
058  @Nullable private final String serviceInstanceId;
059  @Nullable private final String serviceVersion;
060  private final Map<String, String> resourceAttributes;
061
062  private ExporterOpenTelemetryProperties(
063      @Nullable String protocol,
064      @Nullable String endpoint,
065      Map<String, String> headers,
066      @Nullable String interval,
067      @Nullable String timeout,
068      @Nullable String serviceName,
069      @Nullable String serviceNamespace,
070      @Nullable String serviceInstanceId,
071      @Nullable String serviceVersion,
072      Map<String, String> resourceAttributes) {
073    this.protocol = protocol;
074    this.endpoint = endpoint;
075    this.headers = headers;
076    this.interval = interval;
077    this.timeout = timeout;
078    this.serviceName = serviceName;
079    this.serviceNamespace = serviceNamespace;
080    this.serviceInstanceId = serviceInstanceId;
081    this.serviceVersion = serviceVersion;
082    this.resourceAttributes = resourceAttributes;
083  }
084
085  @Nullable
086  public String getProtocol() {
087    return protocol;
088  }
089
090  @Nullable
091  public String getEndpoint() {
092    return endpoint;
093  }
094
095  public Map<String, String> getHeaders() {
096    return headers;
097  }
098
099  @Nullable
100  public String getInterval() {
101    return interval;
102  }
103
104  @Nullable
105  public String getTimeout() {
106    return timeout;
107  }
108
109  @Nullable
110  public String getServiceName() {
111    return serviceName;
112  }
113
114  @Nullable
115  public String getServiceNamespace() {
116    return serviceNamespace;
117  }
118
119  @Nullable
120  public String getServiceInstanceId() {
121    return serviceInstanceId;
122  }
123
124  @Nullable
125  public String getServiceVersion() {
126    return serviceVersion;
127  }
128
129  public Map<String, String> getResourceAttributes() {
130    return resourceAttributes;
131  }
132
133  /**
134   * Note that this will remove entries from {@code propertySource}. This is because we want to know
135   * if there are unused properties remaining after all properties have been loaded.
136   */
137  static ExporterOpenTelemetryProperties load(PropertySource propertySource)
138      throws PrometheusPropertiesException {
139    String protocol = Util.loadString(PREFIX, PROTOCOL, propertySource);
140    String endpoint = Util.loadString(PREFIX, ENDPOINT, propertySource);
141    Map<String, String> headers = Util.loadMap(PREFIX, HEADERS, propertySource);
142    String interval = Util.loadStringAddSuffix(PREFIX, INTERVAL_SECONDS, propertySource, "s");
143    String timeout = Util.loadStringAddSuffix(PREFIX, TIMEOUT_SECONDS, propertySource, "s");
144    String serviceName = Util.loadString(PREFIX, SERVICE_NAME, propertySource);
145    String serviceNamespace = Util.loadString(PREFIX, SERVICE_NAMESPACE, propertySource);
146    String serviceInstanceId = Util.loadString(PREFIX, SERVICE_INSTANCE_ID, propertySource);
147    String serviceVersion = Util.loadString(PREFIX, SERVICE_VERSION, propertySource);
148    Map<String, String> resourceAttributes =
149        Util.loadMap(PREFIX, RESOURCE_ATTRIBUTES, propertySource);
150    return new ExporterOpenTelemetryProperties(
151        protocol,
152        endpoint,
153        headers,
154        interval,
155        timeout,
156        serviceName,
157        serviceNamespace,
158        serviceInstanceId,
159        serviceVersion,
160        resourceAttributes);
161  }
162
163  public static Builder builder() {
164    return new Builder();
165  }
166
167  public static class Builder {
168
169    @Nullable private String protocol;
170    @Nullable private String endpoint;
171    private final Map<String, String> headers = new HashMap<>();
172    @Nullable private String interval;
173    @Nullable private String timeout;
174    @Nullable private String serviceName;
175    @Nullable private String serviceNamespace;
176    @Nullable private String serviceInstanceId;
177    @Nullable private String serviceVersion;
178    private final Map<String, String> resourceAttributes = new HashMap<>();
179
180    private Builder() {}
181
182    /**
183     * The OTLP protocol to use.
184     *
185     * <p>Supported values: {@code "grpc"} or {@code "http/protobuf"}.
186     *
187     * <p>See OpenTelemetry's <a
188     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_protocol">OTEL_EXPORTER_OTLP_PROTOCOL</a>.
189     */
190    public Builder protocol(String protocol) {
191      if (!protocol.equals("grpc") && !protocol.equals("http/protobuf")) {
192        throw new IllegalArgumentException(
193            protocol + ": Unsupported protocol. Expecting grpc or http/protobuf");
194      }
195      this.protocol = protocol;
196      return this;
197    }
198
199    /**
200     * The OTLP endpoint to send metric data to.
201     *
202     * <p>The default depends on the protocol:
203     *
204     * <ul>
205     *   <li>{@code "grpc"}: {@code "http://localhost:4317"}
206     *   <li>{@code "http/protobuf"}: {@code "http://localhost:4318/v1/metrics"}
207     * </ul>
208     *
209     * <p>See OpenTelemetry's <a
210     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_endpoint">OTEL_EXPORTER_OTLP_METRICS_ENDPOINT</a>.
211     */
212    public Builder endpoint(String endpoint) {
213      this.endpoint = endpoint;
214      return this;
215    }
216
217    /**
218     * Add an HTTP header to be applied to outgoing requests. Call multiple times to add multiple
219     * headers.
220     *
221     * <p>See OpenTelemetry's <a
222     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_headers">OTEL_EXPORTER_OTLP_HEADERS</a>.
223     */
224    public Builder header(String name, String value) {
225      this.headers.put(name, value);
226      return this;
227    }
228
229    /**
230     * The interval between the start of two export attempts. Default is 60 seconds.
231     *
232     * <p>Like OpenTelemetry's <a
233     * href="https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#periodic-metric-reader">OTEL_METRIC_EXPORT_INTERVAL</a>
234     * (which defaults to 60000 milliseconds), but specified in seconds rather than milliseconds.
235     */
236    public Builder intervalSeconds(int intervalSeconds) {
237      if (intervalSeconds <= 0) {
238        throw new IllegalArgumentException(intervalSeconds + ": Expecting intervalSeconds > 0");
239      }
240      this.interval = intervalSeconds + "s";
241      return this;
242    }
243
244    /**
245     * The timeout for outgoing requests. Default is 10.
246     *
247     * <p>Like OpenTelemetry's <a
248     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_timeout">OTEL_EXPORTER_OTLP_METRICS_TIMEOUT</a>,
249     * but in seconds rather than milliseconds.
250     */
251    public Builder timeoutSeconds(int timeoutSeconds) {
252      if (timeoutSeconds <= 0) {
253        throw new IllegalArgumentException(timeoutSeconds + ": Expecting timeoutSeconds > 0");
254      }
255      this.timeout = timeoutSeconds + "s";
256      return this;
257    }
258
259    /**
260     * The {@code service.name} resource attribute.
261     *
262     * <p>If not explicitly specified, {@code client_java} will try to initialize it with a
263     * reasonable default, like the JAR file name.
264     *
265     * <p>See {@code service.name} in OpenTelemetry's <a
266     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service">Resource
267     * Semantic Conventions</a>.
268     */
269    public Builder serviceName(String serviceName) {
270      this.serviceName = serviceName;
271      return this;
272    }
273
274    /**
275     * The {@code service.namespace} resource attribute.
276     *
277     * <p>See {@code service.namespace} in OpenTelemetry's <a
278     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
279     * Semantic Conventions</a>.
280     */
281    public Builder serviceNamespace(String serviceNamespace) {
282      this.serviceNamespace = serviceNamespace;
283      return this;
284    }
285
286    /**
287     * The {@code service.instance.id} resource attribute.
288     *
289     * <p>See {@code service.instance.id} in OpenTelemetry's <a
290     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
291     * Semantic Conventions</a>.
292     */
293    public Builder serviceInstanceId(String serviceInstanceId) {
294      this.serviceInstanceId = serviceInstanceId;
295      return this;
296    }
297
298    /**
299     * The {@code service.version} resource attribute.
300     *
301     * <p>See {@code service.version} in OpenTelemetry's <a
302     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
303     * Semantic Conventions</a>.
304     */
305    public Builder serviceVersion(String serviceVersion) {
306      this.serviceVersion = serviceVersion;
307      return this;
308    }
309
310    /**
311     * Add a resource attribute. Call multiple times to add multiple resource attributes.
312     *
313     * <p>See OpenTelemetry's <a
314     * href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration">OTEL_RESOURCE_ATTRIBUTES</a>.
315     */
316    public Builder resourceAttribute(String name, String value) {
317      this.resourceAttributes.put(name, value);
318      return this;
319    }
320
321    public ExporterOpenTelemetryProperties build() {
322      return new ExporterOpenTelemetryProperties(
323          protocol,
324          endpoint,
325          headers,
326          interval,
327          timeout,
328          serviceName,
329          serviceNamespace,
330          serviceInstanceId,
331          serviceVersion,
332          resourceAttributes);
333    }
334  }
335}