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}