cnf 0.6.0

Distribution-agnostic 'command not found'-handler
Documentation
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: (C) 2023 Andreas Hartmann <hartan@7x.de>
// This file is part of cnf, available at <https://gitlab.com/hartang/rust/cnf>

//! # Tools and toys for integrating [`tracing`]
use crate::{Env, config};
use anyhow::Context;
use std::sync::OnceLock;
use tracing_subscriber::{EnvFilter, Layer, filter::DynFilterFn, layer::Filter as TraceFilter};

static ENV_FILTER: OnceLock<Option<EnvFilter>> = OnceLock::new();
static CONFIG_FILTER: OnceLock<tracing::metadata::LevelFilter> = OnceLock::new();

// Internally generic return type to make implementing new layers easier
pub(crate) struct TraceLayer {
    pub layer: Box<dyn Layer<tracing_subscriber::Registry> + Send + Sync>,
    pub guard: Box<dyn Guard>,
}

// Marker trait to stop rust from complaining about `dyn Drop`
pub(crate) trait Guard {}
impl Guard for tracing_appender::non_blocking::WorkerGuard {}

pub(crate) fn logfile() -> anyhow::Result<TraceLayer> {
    let file = std::fs::OpenOptions::new()
        .append(true)
        .create(true)
        .open(&crate::config::get().log_path)
        .with_context(|| {
            format!(
                "failed to open logfile at '{}'",
                crate::config::get().log_path.display()
            )
        })?;
    let (non_blocking, guard) = tracing_appender::non_blocking(file);

    let filter = DynFilterFn::new(|metadata, cx| {
        let env_filter = ENV_FILTER.get_or_init(|| {
            if Env::LogLevel.is_set() {
                Some(EnvFilter::from_env(Env::LogLevel.to_string()))
            } else {
                None
            }
        });
        let config_filter = CONFIG_FILTER.get_or_init(|| *config::get().log_level);

        env_filter
            .as_ref()
            .map(|filter| TraceFilter::enabled(filter, metadata, cx))
            .unwrap_or_else(|| TraceFilter::enabled(config_filter, metadata, cx))
    });

    let layer = tracing_subscriber::fmt::Layer::new()
        .with_writer(non_blocking)
        .with_filter(filter);

    Ok(TraceLayer {
        layer: Box::new(layer),
        guard: Box::new(guard),
    })
}

#[cfg(feature = "debug-flame")]
pub fn flame_file() -> anyhow::Result<std::fs::File> {
    let flame_file = "./cnf_traceinfo.folded";
    std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .open(flame_file)
        .with_context(|| format!("failed to open flamegraph dump at '{}'", flame_file))
}