kvlog 0.1.6

Fast Structual and Hierarchical binary logging for rust
Documentation
use std::{
    ffi::OsStr,
    sync::atomic::{AtomicU8, Ordering},
};

const DISABLED: u8 = 0;
const ENABLED: u8 = 1;
const UNRESOLVED: u8 = 2;

/// Lazily enables logging based on an environment variable.
///
/// An unset environment variable or the exact value `0` disables the guard.
/// Any other present value enables it. The result is cached after the first
/// call to [`is_enabled`](Self::is_enabled), and can be overridden explicitly
/// with [`set`](Self::set).
///
/// Store guards in a `static`, rather than a `const`, so that the cached state
/// and explicit overrides persist across uses.
///
/// # Examples
///
/// ```
/// static AUDIO_LOGS: kvlog::EnvGuard =
///     kvlog::EnvGuard::new("APP_AUDIO_LOGS");
///
/// kvlog::info!(AUDIO_LOGS; "Audio initialized", codec = "opus");
/// ```
#[repr(C)]
pub struct EnvGuard {
    // Keep the state first so a pointer to the guard is also a pointer to the
    // atomic state.
    state: AtomicU8,
    /// The environment variable read when the guard is first evaluated.
    pub name: &'static str,
}

impl EnvGuard {
    /// Creates a guard whose value will be loaded from `name` on first use.
    pub const fn new(name: &'static str) -> EnvGuard {
        Self {
            state: AtomicU8::new(UNRESOLVED),
            name,
        }
    }

    /// Returns whether logging controlled by this guard is enabled.
    #[inline(always)]
    pub fn is_enabled(&self) -> bool {
        match self.state.load(Ordering::Relaxed) {
            DISABLED => false,
            ENABLED => true,
            _ => self.load_from_env(),
        }
    }

    /// Initializes the guard from its environment variable if it is unresolved.
    ///
    /// An explicit value written by [`set`](Self::set) is preserved if it races
    /// with the environment lookup.
    #[inline(never)]
    #[cold]
    pub fn load_from_env(&self) -> bool {
        let enabled = if let Some(value) = std::env::var_os(self.name) {
            if value.as_os_str() != OsStr::new("0") {
                ENABLED
            } else {
                DISABLED
            }
        } else {
            DISABLED
        };

        match self.state.compare_exchange(
            UNRESOLVED,
            enabled,
            Ordering::Relaxed,
            Ordering::Relaxed,
        ) {
            Ok(_) => enabled == ENABLED,
            Err(state) => state == ENABLED,
        }
    }

    /// Overrides the guard with an explicit enabled or disabled value.
    pub fn set(&self, value: bool) {
        self.state.store(
            if value { ENABLED } else { DISABLED },
            Ordering::Relaxed,
        );
    }
}

#[cfg(test)]
mod tests {
    use super::{EnvGuard, ENABLED};
    use std::sync::atomic::Ordering;

    #[test]
    fn loads_and_caches_environment_values() {
        const UNSET: &str = "KVLOG_TEST_ENV_GUARD_UNSET";
        const ZERO: &str = "KVLOG_TEST_ENV_GUARD_ZERO";
        const EMPTY: &str = "KVLOG_TEST_ENV_GUARD_EMPTY";
        const TEXT: &str = "KVLOG_TEST_ENV_GUARD_TEXT";

        std::env::remove_var(UNSET);
        std::env::set_var(ZERO, "0");
        std::env::set_var(EMPTY, "");
        std::env::set_var(TEXT, "enabled");

        assert!(!EnvGuard::new(UNSET).is_enabled());
        assert!(!EnvGuard::new(ZERO).is_enabled());
        assert!(EnvGuard::new(EMPTY).is_enabled());

        let guard = EnvGuard::new(TEXT);
        assert_eq!(guard.name, TEXT);
        assert!(guard.is_enabled());
        std::env::set_var(TEXT, "0");
        assert!(guard.is_enabled());

        std::env::remove_var(ZERO);
        std::env::remove_var(EMPTY);
        std::env::remove_var(TEXT);
    }

    #[test]
    fn explicit_values_override_environment_loading() {
        const FALSE_ENV: &str = "KVLOG_TEST_ENV_GUARD_EXPLICIT_TRUE";
        const TRUE_ENV: &str = "KVLOG_TEST_ENV_GUARD_EXPLICIT_FALSE";

        std::env::set_var(FALSE_ENV, "0");
        std::env::set_var(TRUE_ENV, "1");

        let enabled = EnvGuard::new(FALSE_ENV);
        enabled.set(true);
        assert!(enabled.load_from_env());
        assert_eq!(enabled.state.load(Ordering::Relaxed), ENABLED);

        let disabled = EnvGuard::new(TRUE_ENV);
        disabled.set(false);
        assert!(!disabled.load_from_env());

        let loaded = EnvGuard::new(FALSE_ENV);
        assert!(!loaded.is_enabled());
        loaded.set(true);
        assert!(loaded.is_enabled());

        std::env::remove_var(FALSE_ENV);
        std::env::remove_var(TRUE_ENV);
    }

    #[cfg(unix)]
    #[test]
    fn non_unicode_environment_values_enable_the_guard() {
        use std::{ffi::OsString, os::unix::ffi::OsStringExt};

        const NAME: &str = "KVLOG_TEST_ENV_GUARD_NON_UNICODE";
        std::env::set_var(NAME, OsString::from_vec(vec![0xff]));

        assert!(EnvGuard::new(NAME).is_enabled());

        std::env::remove_var(NAME);
    }
}