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