tracing-kickstart 0.10.0

Bootstrap tracing + OTEL connections. Intended for personal use only.
Documentation
use opentelemetry::{Key, Value};
use secrecy::SecretString;
use serde::{Deserialize, Serialize};
use std::time::Duration;

// !- Builder

pub struct TracingConfigBuilder {
    inner: TracingConfig,
    runtime: TracingConfigOverride,
}
impl TracingConfigBuilder {
    pub fn new(runtime_config: &TracingConfigOverride) -> Self {
        Self {
            inner: TracingConfig {
                filter: None,
                filter_append: runtime_config.filter_append.clone(),
                log_file_path: None,
                ansi_output: true,
                ansi_sanitization: true,
                deployment_env: runtime_config.deployment_env.clone(),
                custom_resource_attrs: None,
                metrics_interval: None,
                otel_config: runtime_config.otel_config.clone(),
            },
            runtime: runtime_config.clone(),
        }
    }

    pub fn build(self) -> TracingConfig {
        // ensure overrides are set - can skip override-only as they are immutable
        let (mut config, runtime) = (self.inner, self.runtime);
        config.filter = runtime.filter.or(config.filter);
        config.log_file_path = runtime.log_file_path.or(config.log_file_path);
        config.ansi_output = runtime.ansi_output.unwrap_or(config.ansi_output);
        config.ansi_sanitization = runtime.ansi_sanitization.unwrap_or(config.ansi_sanitization);
        config.metrics_interval = runtime.metrics_interval.or(config.metrics_interval);

        config
    }

    /// Enables file logging at given path, if provided
    pub fn log_file_path(mut self, path: Option<String>) -> Self {
        self.inner.log_file_path = path;
        self
    }
    /// enables ANSI (color) output in tracing-subscriber
    ///
    /// Default value: `true`
    pub fn ansi_output(mut self, val: bool) -> Self {
        self.inner.ansi_output = val;
        self
    }
    /// enables ANSI sanitization in tracing-subscriber
    ///
    /// Default value: `true`
    pub fn ansi_sanitization(mut self, val: bool) -> Self {
        self.inner.ansi_sanitization = val;
        self
    }
    /// Custom env filter which takes priority over RUST_LOG, if provided
    ///
    /// This is useful for providing a sane default for logging
    ///
    /// It's recommended to use [`TracingOverrideConfig::filter_append`] to make adjustments at runtime.
    pub fn filter(mut self, filter: Option<String>) -> Self {
        self.inner.filter = filter;
        self
    }

    /// If provided, will configure the metrics export period (duration in seconds)
    pub fn metrics_interval(mut self, interval: Option<u64>) -> Self {
        self.inner.metrics_interval = interval;
        self
    }

    /// Custom OTEL resource attributes
    ///
    /// Can only be set using the [`TracingConfigBuilder`]
    pub fn custom_resource_attrs(mut self, attrs: Option<Vec<(Key, Value)>>) -> Self {
        self.inner.custom_resource_attrs = attrs;
        self
    }
}

// !- Tracing config

#[derive(Debug, Clone, Serialize)]
pub struct TracingConfig {
    /// Custom env filter which takes priority over RUST_LOG
    ///
    /// This is beneficial when loading app conf from env,
    /// as it allows overriding the env filter without setting a global RUST_LOG
    pub(crate) filter: Option<String>,

    /// Appends the resolved logging filter with the provided text
    pub(crate) filter_append: Option<String>,

    /// Enables file logging at the provided path
    pub(crate) log_file_path: Option<String>,

    /// tracing-subscriber ANDI output
    pub(crate) ansi_output: bool,

    /// tracing-subscriber ANSI sanitization
    pub(crate) ansi_sanitization: bool,

    /// If set, will be user as the value for the `deployment.environment.name` attribute
    pub(crate) deployment_env: Option<String>,

    /// Custom OTEL resource attributes
    ///
    /// Can only be set using the [`TracingConfigBuildsr`]
    #[serde(skip_serializing)]
    pub(crate) custom_resource_attrs: Option<Vec<(Key, Value)>>,

    /// If set, will configure the metrics export period (duration in seconds)
    pub(crate) metrics_interval: Option<u64>,

    pub(crate) otel_config: Option<TracingOtelConfig>,
}
impl TracingConfig {
    pub fn builder(runtime_config: &TracingConfigOverride) -> TracingConfigBuilder {
        TracingConfigBuilder::new(runtime_config)
    }
    pub fn metrics_interval_duration(&self) -> Option<Duration> {
        self.metrics_interval.map(Duration::from_secs)
    }
    pub fn log_file_path(&self) -> Option<&str> {
        self.log_file_path.as_deref()
    }
    pub fn otel_config(&self) -> &Option<TracingOtelConfig> {
        &self.otel_config
    }
}

// !- Override config

/// Runtime overrides for config
///
/// This is the struct you should use for deserializing env var & .env/config file
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TracingConfigOverride {
    // ! Override-only (set at runtime)

    #[serde(flatten)]
    pub(crate) otel_config: Option<TracingOtelConfig>,

    /// Appends input to tracing filter, preserving the base filter.
    ///
    /// Thls allows for updating the filter at runtime without needing to copy over all the base filters
    ///
    /// A leading comma is **not** required
    pub(crate) filter_append: Option<String>,

    /// If set, will be user as the value for the `deployment.environment.name` attribute
    ///
    /// e.g. prod, staging
    pub(crate) deployment_env: Option<String>,

    // ! Base config overrides

    pub(crate) filter: Option<String>,

    pub(crate) log_file_path: Option<String>,

    pub(crate) ansi_output: Option<bool>,

    pub(crate) ansi_sanitization: Option<bool>,

    pub(crate) metrics_interval: Option<u64>,
}

// !- OTEL config

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracingOtelConfig {
    pub collector_url: String,

    #[serde(default, skip_serializing)]
    pub collector_auth_header: Option<SecretString>,
}