foundations 5.8.1

A Rust service foundations library.
Documentation
use super::channel::{PipelineType, SharedSpanReceiver};
use super::internal::{SharedSpan, Tracer};
use super::live::ActiveRoots;
use super::output_jaeger_thrift_udp;
use super::rate_limit::RateLimitingProbabilisticSampler;
use crate::telemetry::scope::ScopeStack;
use crate::telemetry::settings::{SamplingStrategy, TracesOutput, TracingSettings};
use crate::{BootstrapResult, ServiceInfo};
use cf_rustracing::sampler::{NullSampler, PassiveSampler, Sampler};
use crossbeam_utils::CachePadded;
use futures_util::future::BoxFuture;
use std::sync::{LazyLock, OnceLock};

#[cfg(feature = "telemetry-otlp-grpc")]
use super::output_otlp_grpc;

#[cfg(feature = "user-tracing")]
use super::output_otlp_uds;
#[cfg(feature = "user-tracing")]
use crate::telemetry::settings::{UserTracesOutput, UserTracingSettings};
#[cfg(feature = "user-tracing")]
use cf_rustracing::sampler::AllSampler;

#[cfg(feature = "testing")]
use std::borrow::Cow;

// These singletons are accessed _very often_, and each access requires an atomic load to
// ensure initialization. Make sure nobody else invalidates our cache lines.
static HARNESS: CachePadded<OnceLock<TracingHarness>> = CachePadded::new(OnceLock::new());

#[cfg(feature = "user-tracing")]
static USER_HARNESS: CachePadded<OnceLock<TracingHarness>> = CachePadded::new(OnceLock::new());

static NOOP_HARNESS: CachePadded<LazyLock<TracingHarness>> =
    CachePadded::new(LazyLock::new(|| {
        let (noop_tracer, _) = Tracer::new(NullSampler.boxed());

        TracingHarness {
            tracer: noop_tracer,
            span_scope_stack: Default::default(),

            #[cfg(feature = "testing")]
            test_tracer_scope_stack: Default::default(),

            active_roots: Default::default(),
        }
    }));

#[cfg(feature = "user-tracing")]
static USER_NOOP_HARNESS: CachePadded<LazyLock<TracingHarness>> =
    CachePadded::new(LazyLock::new(|| {
        let (noop_tracer, _) = Tracer::new(NullSampler.boxed());

        TracingHarness {
            tracer: noop_tracer,
            span_scope_stack: Default::default(),

            #[cfg(feature = "testing")]
            test_tracer_scope_stack: Default::default(),

            active_roots: Default::default(),
        }
    }));

pub(crate) struct TracingHarness {
    tracer: Tracer,

    pub(crate) span_scope_stack: ScopeStack<SharedSpan>,

    #[cfg(feature = "testing")]
    pub(crate) test_tracer_scope_stack: ScopeStack<Tracer>,

    pub(crate) active_roots: crate::telemetry::tracing::live::ActiveRoots,
}

impl TracingHarness {
    pub(crate) fn get() -> &'static Self {
        HARNESS.get().unwrap_or_else(|| &**NOOP_HARNESS)
    }

    /// User-tracing harness, or the user no-op harness when the user pipeline isn't initialized.
    #[cfg(feature = "user-tracing")]
    pub(crate) fn get_user() -> &'static Self {
        USER_HARNESS.get().unwrap_or_else(|| &**USER_NOOP_HARNESS)
    }

    #[cfg(feature = "testing")]
    pub(crate) fn tracer(&'static self) -> Cow<'static, Tracer> {
        self.test_tracer_scope_stack
            .current()
            .map(Cow::Owned)
            .unwrap_or_else(|| Cow::Borrowed(&self.tracer))
    }

    #[cfg(not(feature = "testing"))]
    pub(crate) fn tracer(&'static self) -> &'static Tracer {
        &self.tracer
    }
}

pub(super) fn create_tracer_and_span_rx(
    settings: &TracingSettings,
) -> BootstrapResult<(Tracer, SharedSpanReceiver)> {
    let sampler = match &settings.sampling_strategy {
        SamplingStrategy::Passive => PassiveSampler.boxed(),
        SamplingStrategy::Active(settings) => {
            RateLimitingProbabilisticSampler::new(settings)?.boxed()
        }
    };

    if let Some(cap) = settings.max_queue_size {
        let (consumer, span_rx) = super::channel::channel(cap, PipelineType::System);
        let tracer = Tracer::with_consumer(sampler, consumer);
        Ok((tracer, span_rx))
    } else {
        let (consumer, span_rx) = super::channel::unbounded_channel(PipelineType::System);
        let tracer = Tracer::with_consumer(sampler, consumer);
        Ok((tracer, span_rx))
    }
}

pub(super) struct TraceOutputFutures {
    pub initializer: Option<BoxFuture<'static, BootstrapResult<()>>>,
    pub workers: Vec<BoxFuture<'static, ()>>,
}

// NOTE: does nothing if tracing has already been initialized in this process.
pub(crate) fn init(
    service_info: &ServiceInfo,
    settings: &TracingSettings,
) -> BootstrapResult<Option<BoxFuture<'static, BootstrapResult<()>>>> {
    if !settings.enabled || HARNESS.get().is_some() {
        return Ok(None);
    }

    let (tracer, span_rx) = create_tracer_and_span_rx(settings)?;

    let futs = match &settings.output {
        TracesOutput::JaegerThriftUdp(output_settings) => {
            output_jaeger_thrift_udp::start(service_info, output_settings, span_rx)?
        }
        #[cfg(feature = "telemetry-otlp-grpc")]
        TracesOutput::OpenTelemetryGrpc(output_settings) => {
            output_otlp_grpc::start(service_info, output_settings, span_rx)?
        }
    };

    // Only spawn the futures if we are actually initializing the harness
    let mut res = Ok(None);
    HARNESS.get_or_init(|| {
        #[cfg(feature = "metrics")]
        {
            let max_queue_size = settings
                .max_queue_size
                .map(std::num::NonZeroUsize::get)
                .unwrap_or(usize::MAX);
            super::metrics::tracing::max_queue_size(PipelineType::System)
                .set(max_queue_size as u64);
        }

        res = Ok(futs.initializer);
        for f in futs.workers {
            tokio::spawn(f);
        }

        TracingHarness {
            tracer,
            span_scope_stack: Default::default(),

            #[cfg(feature = "testing")]
            test_tracer_scope_stack: Default::default(),

            active_roots: ActiveRoots::new(settings.liveness_tracking.clone()),
        }
    });
    res
}

#[cfg(feature = "user-tracing")]
fn create_tracer_and_span_rx_for_user(
    settings: &UserTracingSettings,
) -> BootstrapResult<(Tracer, SharedSpanReceiver)> {
    // The user pipeline samples everything it receives — the sampling decision
    // is made upstream at activation time, outside foundations.
    let sampler = AllSampler.boxed();

    if let Some(cap) = settings.max_queue_size {
        let (consumer, span_rx) = super::channel::channel(cap, PipelineType::User);
        Ok((Tracer::with_consumer(sampler, consumer), span_rx))
    } else {
        let (consumer, span_rx) = super::channel::unbounded_channel(PipelineType::User);
        Ok((Tracer::with_consumer(sampler, consumer), span_rx))
    }
}

// NOTE: does nothing if user tracing has already been initialized in this process.
#[cfg(feature = "user-tracing")]
pub(crate) fn init_user(
    service_info: &ServiceInfo,
    settings: &UserTracingSettings,
) -> BootstrapResult<Option<BoxFuture<'static, BootstrapResult<()>>>> {
    if !settings.enabled || USER_HARNESS.get().is_some() {
        return Ok(None);
    }

    let (tracer, span_rx) = create_tracer_and_span_rx_for_user(settings)?;

    let futs = match &settings.output {
        UserTracesOutput::OtlpUds(output_settings) => {
            output_otlp_uds::start(service_info, output_settings, span_rx)?
        }
    };

    // Only spawn the futures if we are actually initializing the harness.
    let mut res = Ok(None);
    USER_HARNESS.get_or_init(|| {
        res = Ok(futs.initializer);
        for f in futs.workers {
            tokio::spawn(f);
        }

        TracingHarness {
            tracer,
            span_scope_stack: Default::default(),

            #[cfg(feature = "testing")]
            test_tracer_scope_stack: Default::default(),

            active_roots: Default::default(),
        }
    });
    res
}