use opentelemetry::trace::TracerProvider as _;
use opentelemetry_otlp::{Protocol, WithExportConfig, WithHttpConfig};
pub use opentelemetry_sdk::trace::SdkTracerProvider;
use std::collections::HashMap;
use tracing::info;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
pub fn init_telemetry(enable_otel_export: bool) -> anyhow::Result<Option<SdkTracerProvider>> {
let env_filter = EnvFilter::new(std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()));
if enable_otel_export {
let (tracer, provider) = create_otlp_tracer()?;
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer().compact())
.with(tracing_opentelemetry::layer().with_tracer(tracer))
.try_init()?;
info!("Telemetry initialized with OTLP export enabled");
return Ok(Some(provider));
} else {
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer().compact())
.try_init()?;
info!("Telemetry initialized (OTLP export disabled)");
}
Ok(None)
}
fn create_otlp_tracer() -> anyhow::Result<(opentelemetry_sdk::trace::Tracer, SdkTracerProvider)> {
let service_name = std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| "dwctl".to_string());
let base = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").unwrap_or_else(|_| "http://localhost:4318".to_string());
let endpoint = if base.ends_with("/v1/traces") {
base
} else {
format!("{}/v1/traces", base.trim_end_matches('/'))
};
eprintln!("[OTLP] Initializing OTLP tracer with the following configuration:");
eprintln!("[OTLP] Service Name: {}", service_name);
eprintln!("[OTLP] Endpoint: {}", endpoint);
let mut headers = HashMap::new();
if let Ok(headers_str) = std::env::var("OTEL_EXPORTER_OTLP_HEADERS") {
let decoded = headers_str.replace("%20", " ");
for pair in decoded.split(',') {
if let Some((key, value)) = pair.split_once('=') {
let key = key.trim().to_string();
let value = value.trim().to_string();
headers.insert(key, value);
}
}
eprintln!("[OTLP] Custom headers, length: {}", headers.len());
}
let protocol = match std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref().unwrap_or("http/protobuf") {
"http/protobuf" => Protocol::HttpBinary,
"http/json" => Protocol::HttpJson,
_ => Protocol::HttpBinary,
};
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_http()
.with_endpoint(&endpoint)
.with_protocol(protocol)
.with_headers(headers)
.build()?;
let resource = opentelemetry_sdk::Resource::builder()
.with_service_name(service_name.clone())
.build();
let tracer_provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(resource)
.build();
let tracer = tracer_provider.tracer(service_name);
Ok((tracer, tracer_provider))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn otlp_tracer_builds_with_http_client() {
let (_, provider) = create_otlp_tracer().expect(
"OTLP tracer failed to build - likely a feature flag conflict \
(reqwest-client vs reqwest-blocking-client)",
);
provider.shutdown().ok();
}
#[tokio::test]
async fn otlp_reqwest_supports_https() {
let client = reqwest::Client::new();
let result = client.get("https://example.com").send().await;
assert!(
result.is_ok(),
"reqwest cannot make HTTPS requests — TLS not configured: {}",
result.unwrap_err()
);
}
}