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}