use std::{collections::HashMap, time::Duration};
use crate::config::{OtlpProtocol, SamplingStrategy};
#[derive(Debug, Default)]
pub(crate) struct EnvConfig {
pub service_name: Option<String>,
pub endpoint: Option<String>,
pub protocol: Option<OtlpProtocol>,
pub headers: Option<HashMap<String, String>>,
pub timeout: Option<Duration>,
pub sampler: Option<SamplingStrategy>,
}
pub(crate) fn read_env() -> EnvConfig {
EnvConfig {
service_name: std::env::var("OTEL_SERVICE_NAME").ok(),
endpoint: std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok(),
protocol: std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")
.ok()
.and_then(|s| s.parse().ok()),
headers: parse_headers(),
timeout: std::env::var("OTEL_EXPORTER_OTLP_TIMEOUT")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.map(Duration::from_millis),
sampler: parse_sampler(),
}
}
fn parse_headers() -> Option<HashMap<String, String>> {
let raw = std::env::var("OTEL_EXPORTER_OTLP_HEADERS").ok()?;
let mut headers = HashMap::new();
for pair in raw.split(',') {
if let Some((k, v)) = pair.split_once('=') {
headers.insert(k.trim().to_string(), v.trim().to_string());
}
}
if headers.is_empty() {
None
} else {
Some(headers)
}
}
fn parse_sampler() -> Option<SamplingStrategy> {
let sampler = std::env::var("OTEL_TRACES_SAMPLER").ok()?;
match sampler.to_lowercase().as_str() {
"always_on" => Some(SamplingStrategy::AlwaysOn),
"always_off" => Some(SamplingStrategy::AlwaysOff),
"traceidratio" => {
let ratio = std::env::var("OTEL_TRACES_SAMPLER_ARG")
.ok()
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(1.0);
Some(SamplingStrategy::TraceIdRatio(ratio))
}
"parentbased_always_on" | "parentbased" => Some(SamplingStrategy::ParentBased),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_env_without_vars() {
let env = read_env();
let _ = env;
}
#[test]
fn parse_headers_when_unset() {
if std::env::var("OTEL_EXPORTER_OTLP_HEADERS").is_err() {
assert!(parse_headers().is_none());
}
}
}