1use 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#[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
45static 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 if std::env::var(system::DEBUG).is_ok() || std::env::var(system::DEBUG_SDK).is_ok() {
86 return true;
87 }
88
89 let args: Vec<String> = std::env::args().collect();
91 if args.iter().any(|arg| arg == "--debug" || arg == "-d") {
92 return true;
93 }
94
95 if args.iter().any(|arg| arg.starts_with("--debug=")) {
97 return true;
98 }
99
100 if args.iter().any(|arg| arg.starts_with("--debug-file")) {
102 return true;
103 }
104
105 false
106}
107
108pub 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
119pub 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 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
165pub 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 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 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
224pub 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}