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