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}