Skip to content
Clone
eba4fd1
104 lines 3.3 KB Rust
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<SdkTracerProvider> = 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<SdkTracerProvider> {
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");
}
}