Skip to main content

ai_agent/utils/
debug.rs

1// Source: /data/home/swei/claudecode/openclaudecode/src/skills/bundled/debug.ts
2//! Debug logging utilities
3//!
4//! Translated from openclaudecode/src/utils/debug.ts
5
6use crate::constants::env::{ai, system};
7use crate::utils::debug_filter::{DebugFilter, parse_debug_filter, should_show_debug_message};
8use once_cell::sync::Lazy;
9use std::collections::HashMap;
10use std::sync::Mutex;
11
12/// Debug log level
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum DebugLogLevel {
15    Verbose,
16    Debug,
17    Info,
18    Warn,
19    Error,
20}
21
22impl DebugLogLevel {
23    pub fn from_str(s: &str) -> Self {
24        match s.to_lowercase().as_str() {
25            "verbose" => DebugLogLevel::Verbose,
26            "debug" => DebugLogLevel::Debug,
27            "info" => DebugLogLevel::Info,
28            "warn" => DebugLogLevel::Warn,
29            "error" => DebugLogLevel::Error,
30            _ => DebugLogLevel::Debug,
31        }
32    }
33}
34
35static LEVEL_ORDER: Lazy<HashMap<DebugLogLevel, u8>> = Lazy::new(|| {
36    let mut m = HashMap::new();
37    m.insert(DebugLogLevel::Verbose, 0);
38    m.insert(DebugLogLevel::Debug, 1);
39    m.insert(DebugLogLevel::Info, 2);
40    m.insert(DebugLogLevel::Warn, 3);
41    m.insert(DebugLogLevel::Error, 4);
42    m
43});
44
45/// Minimum log level to include in debug output
46static MIN_DEBUG_LOG_LEVEL: Lazy<Mutex<Option<DebugLogLevel>>> = Lazy::new(|| Mutex::new(None));
47
48pub fn get_min_debug_log_level() -> DebugLogLevel {
49    let mut level = MIN_DEBUG_LOG_LEVEL.lock().unwrap();
50    if let Some(l) = *level {
51        return l;
52    }
53
54    let raw = std::env::var(ai::CODE_DEBUG_LOG_LEVEL)
55        .ok()
56        .map(|s| s.to_lowercase().trim().to_string());
57
58    let l = if let Some(ref raw) = raw {
59        if LEVEL_ORDER.keys().any(|k| {
60            let key = format!("{:?}", k).to_lowercase();
61            key == *raw
62        }) {
63            DebugLogLevel::from_str(raw)
64        } else {
65            DebugLogLevel::Debug
66        }
67    } else {
68        DebugLogLevel::Debug
69    };
70
71    *level = Some(l);
72    l
73}
74
75static RUNTIME_DEBUG_ENABLED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
76
77pub fn is_debug_mode() -> bool {
78    let runtime = *RUNTIME_DEBUG_ENABLED.lock().unwrap();
79
80    if runtime {
81        return true;
82    }
83
84    // Check environment variables
85    if std::env::var(system::DEBUG).is_ok() || std::env::var(system::DEBUG_SDK).is_ok() {
86        return true;
87    }
88
89    // Check command line arguments
90    let args: Vec<String> = std::env::args().collect();
91    if args.iter().any(|arg| arg == "--debug" || arg == "-d") {
92        return true;
93    }
94
95    // Check for --debug=pattern syntax
96    if args.iter().any(|arg| arg.starts_with("--debug=")) {
97        return true;
98    }
99
100    // Check for --debug-file
101    if args.iter().any(|arg| arg.starts_with("--debug-file")) {
102        return true;
103    }
104
105    false
106}
107
108/// Enables debug logging mid-session
109pub fn enable_debug_logging() -> bool {
110    let mut runtime = RUNTIME_DEBUG_ENABLED.lock().unwrap();
111    let was_active = *runtime
112        || std::env::var(ai::USER_TYPE)
113            .map(|v| v == "ant")
114            .unwrap_or(false);
115    *runtime = true;
116    was_active
117}
118
119/// Get debug filter from command line arguments
120pub fn get_debug_filter() -> Option<DebugFilter> {
121    let args: Vec<String> = std::env::args().collect();
122
123    for arg in &args {
124        if arg.starts_with("--debug=") {
125            let pattern = arg.strip_prefix("--debug=").unwrap();
126            return parse_debug_filter(Some(pattern));
127        }
128    }
129
130    None
131}
132
133pub fn is_debug_to_stderr() -> bool {
134    let args: Vec<String> = std::env::args().collect();
135    args.iter()
136        .any(|arg| arg == "--debug-to-stderr" || arg == "-d2e")
137}
138
139pub fn get_debug_file_path() -> Option<String> {
140    let args: Vec<String> = std::env::args().collect();
141
142    for (i, arg) in args.iter().enumerate() {
143        if arg.starts_with("--debug-file=") {
144            return Some(arg.strip_prefix("--debug-file=").unwrap().to_string());
145        }
146        if arg == "--debug-file" && i + 1 < args.len() {
147            return Some(args[i + 1].clone());
148        }
149    }
150
151    None
152}
153
154fn should_log_debug_message(message: &str) -> bool {
155    // Non-ants only write debug logs when debug mode is active
156    let user_type = std::env::var(ai::USER_TYPE).unwrap_or_default();
157    if user_type != "ant" && !is_debug_mode() {
158        return false;
159    }
160
161    let filter = get_debug_filter();
162    should_show_debug_message(message, &filter)
163}
164
165/// Log a debug message
166pub fn log_for_debugging(message: &str, level: DebugLogLevel) {
167    let min_level = get_min_debug_log_level();
168    if LEVEL_ORDER[&level] < LEVEL_ORDER[&min_level] {
169        return;
170    }
171
172    if !should_log_debug_message(message) {
173        return;
174    }
175
176    let timestamp = chrono::Utc::now().to_rfc3339();
177    let level_str = match level {
178        DebugLogLevel::Verbose => "VERBOSE",
179        DebugLogLevel::Debug => "DEBUG",
180        DebugLogLevel::Info => "INFO",
181        DebugLogLevel::Warn => "WARN",
182        DebugLogLevel::Error => "ERROR",
183    };
184    let output = format!("{} [{}] {}\n", timestamp, level_str, message.trim());
185
186    if is_debug_to_stderr() {
187        eprint!("{}", output);
188        return;
189    }
190
191    // Write to debug file
192    let path = get_debug_log_path();
193    if let Some(parent) = std::path::Path::new(&path).parent() {
194        let _ = std::fs::create_dir_all(parent);
195    }
196    let _ = std::fs::OpenOptions::new()
197        .create(true)
198        .append(true)
199        .open(&path)
200        .and_then(|mut f| {
201            use std::io::Write;
202            f.write_all(output.as_bytes())
203        });
204}
205
206pub fn get_debug_log_path() -> String {
207    if let Some(path) = get_debug_file_path() {
208        return path;
209    }
210
211    if let Ok(dir) = std::env::var(ai::CODE_DEBUG_LOGS_DIR) {
212        return dir;
213    }
214
215    // Default path
216    let config_home = std::env::var(ai::CONFIG_HOME)
217        .or_else(|_| std::env::var(ai::CLAUDE_CONFIG_HOME))
218        .or_else(|_| std::env::var(system::HOME).map(|h| format!("{}/.ai", h)))
219        .unwrap_or_else(|_| "~/.ai".to_string());
220
221    format!("{}/debug/debug.txt", config_home)
222}
223
224/// Logs errors for Ants only, always visible in production.
225pub fn log_ant_error(context: &str, error: &str) {
226    let user_type = std::env::var(ai::USER_TYPE).unwrap_or_default();
227    if user_type != "ant" {
228        return;
229    }
230
231    let message = format!("[ANT-ONLY] {} error: {}", context, error);
232    log_for_debugging(&message, DebugLogLevel::Error);
233}