use console::strip_ansi_codes;
use regex::Regex;
use std::sync::LazyLock;
use std::time::Instant;
static LOGRUS_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r#"^time="[^"]*"\s+level=(\w+)\s+msg="((?:[^"\\]|\\.)*)""#).unwrap()
});
const FILTERED_SUBSTRINGS: &[&str] = &["Terminal is not available", "Not forwarding TCP"];
fn format_elapsed(start: &Instant) -> String {
let elapsed = start.elapsed().as_secs();
let minutes = elapsed / 60;
let seconds = elapsed % 60;
format!("[{:02}:{:02}]", minutes, seconds)
}
fn clean_raw_line(line: &str, start: &Instant) -> Option<String> {
let last_segment = line.rsplit('\r').next().unwrap_or(line);
let stripped = strip_ansi_codes(last_segment);
let trimmed = stripped.trim();
if trimmed.is_empty() {
return None;
}
Some(format!(" {} {}", format_elapsed(start), trimmed))
}
pub fn format_lima_log_line(line: &str, start: &Instant) -> Option<String> {
let Some(caps) = LOGRUS_RE.captures(line) else {
return clean_raw_line(line, start);
};
let level = &caps[1];
let msg = caps[2].replace("\\\"", "\"");
let is_low_severity = matches!(level, "info" | "debug" | "trace");
if is_low_severity && FILTERED_SUBSTRINGS.iter().any(|p| msg.contains(p)) {
return None;
}
let prefix = match level {
"warning" | "warn" => "[WARN] ",
"error" => "[ERROR] ",
_ => "",
};
Some(format!(" {} {}{}", format_elapsed(start), prefix, msg))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_info_message() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=info msg="Starting the instance \"wm-415bdd35\" with internal VM driver \"vz\"""#;
assert_eq!(
format_lima_log_line(line, &start),
Some(
r#" [00:00] Starting the instance "wm-415bdd35" with internal VM driver "vz""#
.to_string()
)
);
}
#[test]
fn test_terminal_not_available_filtered() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=info msg="Terminal is not available, proceeding without opening an editor""#;
assert_eq!(format_lima_log_line(line, &start), None);
}
#[test]
fn test_tcp_forwarding_filtered() {
let start = Instant::now();
let line =
r#"time="2026-02-06T07:30:37+02:00" level=info msg="Not forwarding TCP 127.0.0.53:53""#;
assert_eq!(format_lima_log_line(line, &start), None);
}
#[test]
fn test_warning_level() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=warning msg="something went wrong""#;
assert_eq!(
format_lima_log_line(line, &start),
Some(" [00:00] [WARN] something went wrong".to_string())
);
}
#[test]
fn test_error_level() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=error msg="fatal error occurred""#;
assert_eq!(
format_lima_log_line(line, &start),
Some(" [00:00] [ERROR] fatal error occurred".to_string())
);
}
#[test]
fn test_warning_with_noisy_substring_not_filtered() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=warning msg="Not forwarding TCP due to critical issue""#;
assert_eq!(
format_lima_log_line(line, &start),
Some(" [00:00] [WARN] Not forwarding TCP due to critical issue".to_string())
);
}
#[test]
fn test_hostagent_prefix_preserved() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=info msg="[hostagent] Waiting for the essential requirement 1 of 5: \"ssh\"""#;
let result = format_lima_log_line(line, &start).unwrap();
assert!(result.contains("[hostagent]"));
assert!(result.contains("Waiting for the essential requirement"));
}
#[test]
fn test_non_logrus_line_passthrough() {
let start = Instant::now();
let line = "some random output line";
assert_eq!(
format_lima_log_line(line, &start),
Some(" [00:00] some random output line".to_string())
);
}
#[test]
fn test_ansi_escape_stripping() {
let start = Instant::now();
let line = "\x1b[?2026l\x1b[?2026h\r\x1b[1AInstalling Cl\x1b[1Cude C\x1b[1Cde n\x1b[2Cive build\x1b[1Clatest...";
let result = format_lima_log_line(line, &start).unwrap();
assert!(!result.contains('\x1b'));
assert!(result.contains("Installing"));
}
#[test]
fn test_carriage_return_takes_last_segment() {
let start = Instant::now();
let line = "old progress 50%\rnew progress 100%";
assert_eq!(
format_lima_log_line(line, &start),
Some(" [00:00] new progress 100%".to_string())
);
}
#[test]
fn test_blank_after_stripping_filtered() {
let start = Instant::now();
let line = "\x1b[?2026h\r\x1b[1A";
assert_eq!(format_lima_log_line(line, &start), None);
}
#[test]
fn test_extra_key_values_ignored() {
let start = Instant::now();
let line = r#"time="2026-02-06T07:30:37+02:00" level=info msg="Attempting to download the image" arch=aarch64 digest= location="https://example.com/image.qcow2""#;
assert_eq!(
format_lima_log_line(line, &start),
Some(" [00:00] Attempting to download the image".to_string())
);
}
#[test]
fn test_format_elapsed() {
let start = Instant::now();
let result = format_elapsed(&start);
assert_eq!(result, "[00:00]");
}
}