use opentelemetry::trace::TracerProvider as _;
use opentelemetry_otlp::{ExporterBuildError, SpanExporter, WithExportConfig};
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider};
use tracing_subscriber::{Layer, Registry};
#[derive(Debug, Clone)]
pub struct TelemetryConfig {
pub endpoint: String,
pub service_name: String,
pub sample_rate: f64,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
endpoint: "http://localhost:4317".to_string(),
service_name: "rapina".to_string(),
sample_rate: 1.0,
}
}
}
impl TelemetryConfig {
pub fn new() -> Self {
Self::default()
}
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
pub fn service_name(mut self, name: impl Into<String>) -> Self {
self.service_name = name.into();
self
}
pub fn sample_rate(mut self, rate: f64) -> Self {
self.sample_rate = rate;
self
}
}
fn normalize_sample_rate(rate: f64) -> f64 {
if rate.is_nan() {
1.0
} else {
rate.clamp(0.0, 1.0)
}
}
pub(crate) fn build_otel_pipeline(
config: &TelemetryConfig,
) -> Result<(SdkTracerProvider, Box<dyn Layer<Registry> + Send + Sync>), ExporterBuildError> {
let exporter = SpanExporter::builder()
.with_tonic()
.with_endpoint(&config.endpoint)
.build()?;
let resource = Resource::builder()
.with_service_name(config.service_name.clone())
.build();
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
normalize_sample_rate(config.sample_rate),
))))
.with_resource(resource)
.build();
let tracer = provider.tracer("rapina");
let layer = tracing_opentelemetry::layer().with_tracer(tracer).boxed();
Ok((provider, layer))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_values() {
let config = TelemetryConfig::default();
assert_eq!(config.endpoint, "http://localhost:4317");
assert_eq!(config.service_name, "rapina");
assert_eq!(config.sample_rate, 1.0);
}
#[test]
fn builder_overrides_fields() {
let config = TelemetryConfig::new()
.endpoint("http://jaeger:4317")
.service_name("my-api")
.sample_rate(0.25);
assert_eq!(config.endpoint, "http://jaeger:4317");
assert_eq!(config.service_name, "my-api");
assert_eq!(config.sample_rate, 0.25);
}
#[test]
fn sample_rate_is_normalized() {
assert_eq!(normalize_sample_rate(0.5), 0.5);
assert_eq!(normalize_sample_rate(-1.0), 0.0);
assert_eq!(normalize_sample_rate(2.0), 1.0);
assert_eq!(normalize_sample_rate(f64::NAN), 1.0);
}
}