use std::time::Duration;
use opentelemetry_otlp::WithExportConfig;
use super::MetricsError;
use super::otel_types::{OtelMetricsConfig, OtelProtocol};
struct ResolvedOtelConfig {
endpoint: String,
protocol: OtelProtocol,
export_interval: Duration,
service_name: String,
}
fn resolve_config(config: &OtelMetricsConfig) -> ResolvedOtelConfig {
let endpoint =
std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").unwrap_or_else(|_| config.endpoint.clone());
let protocol = std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")
.ok()
.and_then(|p| match p.as_str() {
"grpc" => Some(OtelProtocol::Grpc),
"http/protobuf" | "http" => Some(OtelProtocol::Http),
_ => None,
})
.unwrap_or(config.protocol);
let export_interval = std::env::var("OTEL_METRIC_EXPORT_INTERVAL")
.ok()
.and_then(|v| v.parse::<u64>().ok())
.map_or_else(
|| Duration::from_secs(config.export_interval_secs),
Duration::from_millis,
);
let service_name =
std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| config.service_name.clone());
ResolvedOtelConfig {
endpoint,
protocol,
export_interval,
service_name,
}
}
fn build_otlp_exporter(
protocol: OtelProtocol,
endpoint: &str,
) -> Result<opentelemetry_otlp::MetricExporter, MetricsError> {
match protocol {
OtelProtocol::Grpc => opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.map_err(|e| MetricsError::BuildError(format!("OTel gRPC exporter: {e}"))),
OtelProtocol::Http => opentelemetry_otlp::MetricExporter::builder()
.with_http()
.with_endpoint(endpoint)
.build()
.map_err(|e| MetricsError::BuildError(format!("OTel HTTP exporter: {e}"))),
}
}
pub(crate) fn build_otel_recorder(
scope_name: &str,
config: &OtelMetricsConfig,
) -> Result<
(
metrics_exporter_opentelemetry::Recorder,
opentelemetry_sdk::metrics::SdkMeterProvider,
),
MetricsError,
> {
let resolved = resolve_config(config);
let exporter = build_otlp_exporter(resolved.protocol, &resolved.endpoint)?;
let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter)
.with_interval(resolved.export_interval)
.build();
let mut resource_builder = opentelemetry_sdk::Resource::builder();
if !resolved.service_name.is_empty() {
resource_builder = resource_builder.with_service_name(resolved.service_name);
}
let resource = resource_builder.build();
let reader_for_closure = reader;
let resource_for_closure = resource;
let (provider, recorder) =
metrics_exporter_opentelemetry::Recorder::builder(scope_name.to_string())
.with_meter_provider(move |mpb| {
mpb.with_reader(reader_for_closure)
.with_resource(resource_for_closure)
})
.build();
tracing::info!(
endpoint = %resolved.endpoint,
protocol = ?resolved.protocol,
export_interval_secs = resolved.export_interval.as_secs(),
"OTel metrics recorder built"
);
Ok((recorder, provider))
}