001package io.prometheus.metrics.exporter.opentelemetry;
002
003import io.opentelemetry.sdk.metrics.export.MetricReader;
004import io.prometheus.metrics.annotations.StableApi;
005import io.prometheus.metrics.config.PrometheusProperties;
006import io.prometheus.metrics.model.registry.PrometheusRegistry;
007import java.util.HashMap;
008import java.util.Map;
009import javax.annotation.Nullable;
010
011public class OpenTelemetryExporter implements AutoCloseable {
012  private final MetricReader reader;
013
014  /**
015   * @deprecated This constructor is not part of the stable API. Use {@link #builder()} or {@link
016   *     #builder(PrometheusProperties)} instead.
017   */
018  @Deprecated
019  public OpenTelemetryExporter(MetricReader reader) {
020    this.reader = reader;
021  }
022
023  @StableApi
024  @Override
025  public void close() {
026    reader.shutdown();
027  }
028
029  @StableApi
030  public static Builder builder() {
031    return new Builder(PrometheusProperties.get());
032  }
033
034  @StableApi
035  public static Builder builder(PrometheusProperties config) {
036    return new Builder(config);
037  }
038
039  @StableApi
040  public static class Builder {
041
042    private final PrometheusProperties config;
043    @Nullable private PrometheusRegistry registry = null;
044    @Nullable String protocol;
045    @Nullable String endpoint;
046    final Map<String, String> headers = new HashMap<>();
047    @Nullable String interval;
048    @Nullable String timeout;
049    @Nullable String serviceName;
050    @Nullable String serviceNamespace;
051    @Nullable String serviceInstanceId;
052    @Nullable String serviceVersion;
053    final Map<String, String> resourceAttributes = new HashMap<>();
054    @Nullable Boolean preserveNames;
055
056    private Builder(PrometheusProperties config) {
057      this.config = config;
058    }
059
060    public Builder registry(PrometheusRegistry registry) {
061      this.registry = registry;
062      return this;
063    }
064
065    /**
066     * Specifies the OTLP transport protocol to be used when exporting metrics.
067     *
068     * <p>Supported values are {@code "grpc"} and {@code "http/protobuf"}. Default is {@code
069     * "grpc"}.
070     *
071     * <p>See OpenTelemetry's <a
072     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_protocol">OTEL_EXPORTER_OTLP_PROTOCOL</a>.
073     */
074    public Builder protocol(String protocol) {
075      if (!protocol.equals("grpc") && !protocol.equals("http/protobuf")) {
076        throw new IllegalArgumentException(
077            protocol + ": Unsupported protocol. Expecting grpc or http/protobuf");
078      }
079      this.protocol = protocol;
080      return this;
081    }
082
083    /**
084     * The OTLP endpoint to send metric data to.
085     *
086     * <p>The default depends on the protocol:
087     *
088     * <ul>
089     *   <li>{@code "grpc"}: {@code "http://localhost:4317"}
090     *   <li>{@code "http/protobuf"}: {@code "http://localhost:4318/v1/metrics"}
091     * </ul>
092     *
093     * If the protocol is {@code "http/protobuf"} and the endpoint does not have the {@code
094     * "/v1/metrics"} suffix, the {@code "/v1/metrics"} suffix will automatically be appended.
095     *
096     * <p>See OpenTelemetry's <a
097     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_endpoint">OTEL_EXPORTER_OTLP_METRICS_ENDPOINT</a>.
098     */
099    public Builder endpoint(String endpoint) {
100      this.endpoint = endpoint;
101      return this;
102    }
103
104    /**
105     * Add an HTTP header to be applied to outgoing requests. Call multiple times to add multiple
106     * headers.
107     *
108     * <p>See OpenTelemetry's <a
109     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_headers">OTEL_EXPORTER_OTLP_HEADERS</a>.
110     */
111    public Builder header(String name, String value) {
112      this.headers.put(name, value);
113      return this;
114    }
115
116    /**
117     * The interval between the start of two export attempts. Default is 60000.
118     *
119     * <p>Like OpenTelemetry's <a
120     * href="https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#periodic-metric-reader">OTEL_METRIC_EXPORT_INTERVAL</a>,
121     * but in seconds rather than milliseconds.
122     */
123    public Builder intervalSeconds(int intervalSeconds) {
124      if (intervalSeconds <= 0) {
125        throw new IllegalStateException(intervalSeconds + ": expecting a push interval > 0s");
126      }
127      this.interval = intervalSeconds + "s";
128      return this;
129    }
130
131    /**
132     * The timeout for outgoing requests. Default is 10.
133     *
134     * <p>Like OpenTelemetry's <a
135     * href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_timeout">OTEL_EXPORTER_OTLP_METRICS_TIMEOUT</a>,
136     * but in seconds rather than milliseconds.
137     */
138    public Builder timeoutSeconds(int timeoutSeconds) {
139      if (timeoutSeconds <= 0) {
140        throw new IllegalStateException(timeoutSeconds + ": expecting a push interval > 0s");
141      }
142      this.timeout = timeoutSeconds + "s";
143      return this;
144    }
145
146    /**
147     * The {@code service.name} resource attribute.
148     *
149     * <p>If not explicitly specified, {@code client_java} will try to initialize it with a
150     * reasonable default, like the JAR file name.
151     *
152     * <p>See {@code service.name} in OpenTelemetry's <a
153     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service">Resource
154     * Semantic Conventions</a>.
155     */
156    public Builder serviceName(String serviceName) {
157      this.serviceName = serviceName;
158      return this;
159    }
160
161    /**
162     * The {@code service.namespace} resource attribute.
163     *
164     * <p>See {@code service.namespace} in OpenTelemetry's <a
165     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
166     * Semantic Conventions</a>.
167     */
168    public Builder serviceNamespace(String serviceNamespace) {
169      this.serviceNamespace = serviceNamespace;
170      return this;
171    }
172
173    /**
174     * The {@code service.instance.id} resource attribute.
175     *
176     * <p>See {@code service.instance.id} in OpenTelemetry's <a
177     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
178     * Semantic Conventions</a>.
179     */
180    public Builder serviceInstanceId(String serviceInstanceId) {
181      this.serviceInstanceId = serviceInstanceId;
182      return this;
183    }
184
185    /**
186     * The {@code service.version} resource attribute.
187     *
188     * <p>See {@code service.version} in OpenTelemetry's <a
189     * href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
190     * Semantic Conventions</a>.
191     */
192    public Builder serviceVersion(String serviceVersion) {
193      this.serviceVersion = serviceVersion;
194      return this;
195    }
196
197    /**
198     * Add a resource attribute. Call multiple times to add multiple resource attributes.
199     *
200     * <p>See OpenTelemetry's <a
201     * href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration">OTEL_RESOURCE_ATTRIBUTES</a>.
202     */
203    public Builder resourceAttribute(String name, String value) {
204      this.resourceAttributes.put(name, value);
205      return this;
206    }
207
208    /**
209     * When {@code true}, metric names are preserved as-is (including suffixes like {@code _total}).
210     * When {@code false} (default), standard OTel name normalization is applied.
211     */
212    public Builder preserveNames(boolean preserveNames) {
213      this.preserveNames = preserveNames;
214      return this;
215    }
216
217    public OpenTelemetryExporter buildAndStart() {
218      if (registry == null) {
219        registry = PrometheusRegistry.defaultRegistry;
220      }
221      return new OpenTelemetryExporter(OtelAutoConfig.createReader(this, config, registry));
222    }
223  }
224}