返回 CodeWhale
logging.rs
根目录 / crates / tui / src / logging.rs
1 //! Lightweight verbose logging helpers for the CLI.
2
3 use std::sync::atomic::{AtomicBool, Ordering};
4
5 use colored::Colorize;
6
7 use crate::palette;
8 static VERBOSE: AtomicBool = AtomicBool::new(false);
9 #[cfg(windows)]
10 static VERBOSE_SNAPSHOT: AtomicBool = AtomicBool::new(false);
11
12 /// Enable or disable verbose logging output.
13 pub fn set_verbose(enabled: bool) {
14 VERBOSE.store(enabled, Ordering::SeqCst);
15 }
16
17 /// Capture the current verbose state so the TUI can restore it after
18 /// temporarily suppressing Windows alt-screen output.
19 #[cfg(windows)]
20 pub fn snapshot_verbose_state() {
21 VERBOSE_SNAPSHOT.store(is_verbose(), Ordering::SeqCst);
22 }
23
24 /// Restore the last captured verbose state.
25 #[cfg(windows)]
26 pub fn restore_verbose_state() {
27 set_verbose(VERBOSE_SNAPSHOT.load(Ordering::SeqCst));
28 }
29
30 /// Return true when `CODEWHALE_LOG_LEVEL` (legacy alias: `DEEPSEEK_LOG_LEVEL`)
31 /// requests verbose output.
32 ///
33 /// Note: `RUST_LOG` is intentionally NOT checked here — it controls the
34 /// `tracing` subscriber filter in `runtime_log.rs` (file logging) and
35 /// should not gate CLI verbose output. On Windows, where stderr is not
36 /// redirected to the log file, coupling the two causes tracing log
37 /// messages to leak into the TUI alt-screen.
38 #[must_use]
39 pub fn env_requests_verbose_logging() -> bool {
40 std::env::var("CODEWHALE_LOG_LEVEL")
41 .or_else(|_| std::env::var("DEEPSEEK_LOG_LEVEL"))
42 .ok()
43 .is_some_and(|value| log_value_enables_verbose(&value))
44 }
45
46 fn log_value_enables_verbose(value: &str) -> bool {
47 value.split(',').any(|directive| {
48 let level = directive
49 .rsplit('=')
50 .next()
51 .unwrap_or(directive)
52 .trim()
53 .to_ascii_lowercase();
54 matches!(level.as_str(), "trace" | "debug" | "info")
55 })
56 }
57
58 /// Check whether verbose logging is enabled.
59 #[must_use]
60 pub fn is_verbose() -> bool {
61 VERBOSE.load(Ordering::SeqCst)
62 }
63
64 /// Emit a verbose info message (no-op when verbosity is disabled).
65 pub fn info(message: impl AsRef<str>) {
66 if is_verbose() {
67 let (r, g, b) = palette::WHALE_INFO_RGB;
68 eprintln!("{} {}", "info".truecolor(r, g, b).bold(), message.as_ref());
69 }
70 }
71
72 /// Emit a verbose warning message (no-op when verbosity is disabled).
73 pub fn warn(message: impl AsRef<str>) {
74 if is_verbose() {
75 let (r, g, b) = palette::WHALE_INFO_RGB;
76 eprintln!("{} {}", "warn".truecolor(r, g, b).bold(), message.as_ref());
77 }
78 }
79
80 #[cfg(test)]
81 #[cfg(windows)]
82 mod tests {
83 use super::*;
84 use std::sync::Mutex;
85
86 static TEST_GUARD: Mutex<()> = Mutex::new(());
87
88 #[test]
89 fn log_value_parser_accepts_common_rust_log_directives() {
90 assert!(log_value_enables_verbose("debug"));
91 assert!(log_value_enables_verbose("codewhale_cli=debug"));
92 assert!(log_value_enables_verbose(
93 "warn,codewhale_tui::client=trace"
94 ));
95 assert!(!log_value_enables_verbose("warn"));
96 assert!(!log_value_enables_verbose("codewhale_tui=off"));
97 }
98
99 #[test]
100 fn snapshot_and_restore_verbose_state_round_trip() {
101 let _guard = TEST_GUARD.lock().unwrap_or_else(|err| err.into_inner());
102
103 set_verbose(false);
104 snapshot_verbose_state();
105 set_verbose(true);
106 restore_verbose_state();
107 assert!(!is_verbose());
108
109 set_verbose(true);
110 snapshot_verbose_state();
111 set_verbose(false);
112 restore_verbose_state();
113 assert!(is_verbose());
114
115 set_verbose(false);
116 }
117
118 #[test]
119 fn restore_keeps_cli_verbose_state_even_when_env_is_not_verbose() {
120 let _guard = TEST_GUARD.lock().unwrap_or_else(|err| err.into_inner());
121
122 set_verbose(true);
123 snapshot_verbose_state();
124
125 // Simulate the Windows alt-screen suppression path. The restore must
126 // bring back the pre-suppression CLI state without depending on the
127 // environment.
128 set_verbose(false);
129 restore_verbose_state();
130
131 assert!(is_verbose());
132 set_verbose(false);
133 }
134 }
135
135 lines RUST