Skip to main content

lino_objects_codec/
debug.rs

1//! Opt-in tracing for the codec.
2//!
3//! Tracing is off by default and writes nothing. It is turned on either by
4//! setting the `LINO_CODEC_DEBUG` environment variable to a truthy value
5//! (`1`, `true`, `yes`, `on`) or by calling [`set_debug_enabled`] from code.
6//!
7//! The same switch and the same environment variable exist in the JavaScript,
8//! Python and C# implementations, so a problem can be traced the same way in
9//! every language.
10
11use std::sync::atomic::{AtomicU8, Ordering};
12
13/// Name of the environment variable that turns tracing on.
14pub const DEBUG_ENV_VAR: &str = "LINO_CODEC_DEBUG";
15
16const TRUTHY: [&str; 4] = ["1", "true", "yes", "on"];
17
18/// `0` = follow the environment, `1` = forced off, `2` = forced on.
19static OVERRIDE: AtomicU8 = AtomicU8::new(0);
20
21/// Whether tracing is currently on.
22pub fn is_debug_enabled() -> bool {
23    match OVERRIDE.load(Ordering::Relaxed) {
24        1 => false,
25        2 => true,
26        _ => std::env::var(DEBUG_ENV_VAR)
27            .is_ok_and(|raw| TRUTHY.contains(&raw.trim().to_ascii_lowercase().as_str())),
28    }
29}
30
31/// Turn tracing on or off from code, overriding the environment variable.
32///
33/// # Arguments
34///
35/// * `enabled` - `Some(true)`/`Some(false)` to force, `None` to follow the environment
36pub fn set_debug_enabled(enabled: Option<bool>) {
37    let value = match enabled {
38        None => 0,
39        Some(false) => 1,
40        Some(true) => 2,
41    };
42    OVERRIDE.store(value, Ordering::Relaxed);
43}
44
45/// Emit a trace message when tracing is on.
46///
47/// The message is built by a closure so that building it costs nothing while
48/// tracing is off, which is the normal case.
49///
50/// # Arguments
51///
52/// * `scope` - Where the message comes from, for example `readable.decode`
53/// * `message` - Builds the message text
54pub fn trace<F: FnOnce() -> String>(scope: &str, message: F) {
55    if !is_debug_enabled() {
56        return;
57    }
58    eprintln!("[lino-codec] {}: {}", scope, message());
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use std::sync::Mutex;
65
66    // Both tests mutate one process-wide override. Keep their assertions inside
67    // the same lock so the parallel test harness cannot reset another test's flag.
68    static DEBUG_TEST_LOCK: Mutex<()> = Mutex::new(());
69
70    #[test]
71    fn override_turns_tracing_on_and_off() {
72        let _guard = DEBUG_TEST_LOCK.lock().unwrap();
73        set_debug_enabled(Some(true));
74        assert!(is_debug_enabled());
75        set_debug_enabled(Some(false));
76        assert!(!is_debug_enabled());
77        set_debug_enabled(None);
78    }
79
80    #[test]
81    fn trace_builds_nothing_while_off() {
82        let _guard = DEBUG_TEST_LOCK.lock().unwrap();
83        set_debug_enabled(Some(false));
84        trace("test", || panic!("must not be called while tracing is off"));
85        set_debug_enabled(None);
86    }
87}