Skip to main content

aurora_autodebug/
analyzer.rs

1use std::fs;
2use std::path::Path;
3use std::io::{BufRead, BufReader};
4use aurora_core::{AuroraResult, AuroraError, Pipeline, Value};
5use regex::Regex;
6
7pub fn analyze_target(target: &str) -> AuroraResult<Pipeline> {
8    if target.chars().all(|c| c.is_ascii_digit()) {
9        return analyze_process(target);
10    }
11
12    let path = Path::new(target);
13    if path.exists() {
14        if path.is_file() {
15            let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
16            match ext {
17                "log" | "txt" => analyze_log_file(target),
18                _ => {
19                    let size = fs::metadata(target)
20                        .map(|m| m.len())
21                        .unwrap_or(0);
22                    Ok(Pipeline::single(Value::String(
23                        format!("File: {target} ({size} bytes). Use 'aurora autodebug log {target}' to analyze.")
24                    )))
25                }
26            }
27        } else {
28            Err(AuroraError::AutoDebugError(format!("Not a file: {target}")))
29        }
30    } else {
31        Err(AuroraError::NotFound(format!("Target not found: {target}")))
32    }
33}
34
35fn analyze_process(pid: &str) -> AuroraResult<Pipeline> {
36    let status_path = format!("/proc/{pid}/status");
37    let status = match fs::read_to_string(&status_path) {
38        Ok(s) => s,
39        Err(_) => return Err(AuroraError::AutoDebugError(format!("Process {pid} not found"))),
40    };
41
42    let mut results = Vec::new();
43    for line in status.lines() {
44        if let Some((key, val)) = line.split_once(":\t") {
45            let mut record = std::collections::BTreeMap::new();
46            record.insert("key".to_string(), Value::String(key.trim().to_string()));
47            record.insert("value".to_string(), Value::String(val.trim().to_string()));
48            results.push(Value::Record(record));
49        }
50    }
51
52    Ok(Pipeline::table(
53        vec!["key".to_string(), "value".to_string()],
54        results.into_iter().map(|r| {
55            match r {
56                Value::Record(map) => {
57                    vec![
58                        map.get("key").cloned().unwrap_or(Value::Null),
59                        map.get("value").cloned().unwrap_or(Value::Null),
60                    ]
61                }
62                _ => vec![],
63            }
64        }).collect(),
65    ))
66}
67
68pub fn analyze_log_file(path: &str) -> AuroraResult<Pipeline> {
69    let file = fs::File::open(path)
70        .map_err(|e| AuroraError::AutoDebugError(format!("Cannot open log: {e}")))?;
71    let reader = BufReader::new(file);
72
73    let error_re = Regex::new(r"(?i)(ERROR|FATAL|PANIC|CRASH|SEGFAULT|SEGV|CORE DUMP|KILLED|OOM)").ok();
74    let warn_re = Regex::new(r"(?i)(WARNING|WARN|DEPRECATED|SLOW)").ok();
75
76    let mut results = Vec::new();
77    for (i, line_result) in reader.lines().enumerate() {
78        let line = line_result.map_err(|e| AuroraError::AutoDebugError(format!("Read error: {e}")))?;
79        if let Some(ref re) = error_re {
80            if let Some(m) = re.find(&line) {
81                let mut record = std::collections::BTreeMap::new();
82                record.insert("severity".to_string(), Value::String("ERROR".to_string()));
83                record.insert("line".to_string(), Value::Int((i + 1) as i64));
84                record.insert("match".to_string(), Value::String(m.as_str().to_string()));
85                record.insert("content".to_string(), Value::String(line.clone()));
86                results.push(Value::Record(record));
87            }
88        }
89        if let Some(ref re) = warn_re {
90            if let Some(m) = re.find(&line) {
91                let mut record = std::collections::BTreeMap::new();
92                record.insert("severity".to_string(), Value::String("WARNING".to_string()));
93                record.insert("line".to_string(), Value::Int((i + 1) as i64));
94                record.insert("match".to_string(), Value::String(m.as_str().to_string()));
95                record.insert("content".to_string(), Value::String(line));
96                results.push(Value::Record(record));
97            }
98        }
99    }
100
101    if results.is_empty() {
102        return Ok(Pipeline::single(Value::String(
103            "No errors or warnings found in log.".to_string()
104        )));
105    }
106
107    Ok(Pipeline::table(
108        vec!["severity".to_string(), "line".to_string(), "match".to_string(), "content".to_string()],
109        results.into_iter().map(|r| {
110            match r {
111                Value::Record(map) => {
112                    vec![
113                        map.get("severity").cloned().unwrap_or(Value::Null),
114                        map.get("line").cloned().unwrap_or(Value::Null),
115                        map.get("match").cloned().unwrap_or(Value::Null),
116                        map.get("content").cloned().unwrap_or(Value::Null),
117                    ]
118                }
119                _ => vec![],
120            }
121        }).collect(),
122    ))
123}
124
125pub fn find_log_files(path: &str) -> Vec<String> {
126    let mut logs = Vec::new();
127    if let Ok(entries) = fs::read_dir(path) {
128        for entry in entries.flatten() {
129            let p = entry.path();
130            if let Some(ext) = p.extension() {
131                if ext == "log" {
132                    logs.push(p.to_string_lossy().to_string());
133                }
134            }
135        }
136    }
137    logs
138}