use std::sync::OnceLock; use opentelemetry::trace::TracerProvider as _; use opentelemetry_sdk::trace::SdkTracerProvider; use tracing::{info, warn}; use tracing_subscriber::EnvFilter; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; /// Installed tracer provider, kept so shutdown can flush the batch processor. static TRACER_PROVIDER: OnceLock = OnceLock::new(); pub fn init_tracing() { // The filter is installed on the registry rather than on the fmt layer so it // governs span *creation*: without it every debug-level span in the process // (hyper/tower internals) becomes an exported span. let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let subscriber = tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer()) .with(filter); match tracer_provider() { Some(provider) => { let _ = TRACER_PROVIDER.set(provider.clone()); subscriber .with(tracing_opentelemetry::layer().with_tracer(provider.tracer("gitdata"))) .init(); info!("opentelemetry trace export enabled"); } None => subscriber.init(), } } /// Traces are opt-in through the standard OTLP environment variables: setting /// `OTEL_EXPORTER_OTLP_ENDPOINT` turns export on, anything else (including a bad /// endpoint) only downgrades to log-only. Telemetry must never stop a host from /// booting, so every failure path here logs and returns `None`. fn tracer_provider() -> Option { if std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") .unwrap_or_default() .is_empty() { return None; } let exporter = match opentelemetry_otlp::SpanExporter::builder() .with_http() .build() { Ok(exporter) => exporter, Err(err) => { eprintln!("failed to build otlp span exporter, traces disabled: {err}"); return None; } }; Some( SdkTracerProvider::builder() .with_batch_exporter(exporter) .build(), ) } pub async fn shutdown_signal() { let ctrl_c = async { if let Err(err) = tokio::signal::ctrl_c().await { warn!(error = %err, "failed to install ctrl_c handler"); } }; #[cfg(unix)] let terminate = async { use tokio::signal::unix::{SignalKind, signal}; match signal(SignalKind::terminate()) { Ok(mut s) => { s.recv().await; } Err(err) => { warn!(error = %err, "failed to install SIGTERM handler"); } } }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => info!("received SIGINT"), _ = terminate => info!("received SIGTERM"), } flush_traces(); } /// Drains spans queued in the batch processor. Without this the tail of every /// deployment's trace output is lost on rollout, which is exactly when you look /// at traces. pub fn flush_traces() { if let Some(provider) = TRACER_PROVIDER.get() && let Err(err) = provider.shutdown() { warn!(error = %err, "failed to flush tracer provider"); } }