cnf 0.6.1

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::level_filters::LevelFilter;
use tracing_subscriber::{EnvFilter, Layer};

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(cli_debug: bool) -> 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 layer = tracing_subscriber::fmt::Layer::new().with_writer(non_blocking);

    let filtered: Box<dyn Layer<tracing_subscriber::Registry> + Send + Sync> = if cli_debug {
        Box::new(layer.with_filter(LevelFilter::DEBUG))
    } else if let Some(level) = Env::LogLevel
        .get::<String>()
        .or_else(|| Env::RustLog.get::<String>())
        .map(EnvFilter::new)
    {
        Box::new(layer.with_filter(level))
    } else {
        Box::new(layer.with_filter(*CONFIG_FILTER.get_or_init(|| *config::get().log_level)))
    };

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

#[cfg(feature = "debug-flame")]
pub(crate) 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))
}