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