Skip to content

Commit d98cce0

Browse files
author
Greyforge Admin
committed
Match log levels exactly
1 parent 7954d02 commit d98cce0

2 files changed

Lines changed: 94 additions & 2 deletions

File tree

src/cortex-cli/src/logs_cmd.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ fn format_size(bytes: u64) -> String {
8181
}
8282
}
8383

84+
fn parse_log_level(line: &str) -> Option<&str> {
85+
line.split(|c: char| !c.is_ascii_alphanumeric())
86+
.find(|part| matches!(*part, "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR"))
87+
}
88+
89+
fn line_matches_level(line: &str, level: &str) -> bool {
90+
parse_log_level(&line.to_uppercase()).is_some_and(|line_level| line_level == level)
91+
}
92+
8493
impl LogsCli {
8594
/// Run the logs command.
8695
pub async fn run(self) -> Result<()> {
@@ -174,7 +183,7 @@ impl LogsCli {
174183
let level_upper = level.to_uppercase();
175184
lines
176185
.iter()
177-
.filter(|line| line.to_uppercase().contains(&level_upper))
186+
.filter(|line| line_matches_level(line, &level_upper))
178187
.copied()
179188
.collect()
180189
} else {
@@ -240,7 +249,7 @@ impl LogsCli {
240249
Ok(_) => {
241250
// Apply level filter if specified
242251
if let Some(ref level) = level_filter
243-
&& !line.to_uppercase().contains(&level.to_uppercase())
252+
&& !line_matches_level(&line, &level.to_uppercase())
244253
{
245254
continue;
246255
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::fs;
2+
use std::process::Command;
3+
4+
use tempfile::tempdir;
5+
6+
fn combined_output(output: &std::process::Output) -> String {
7+
format!(
8+
"{}{}",
9+
String::from_utf8_lossy(&output.stdout),
10+
String::from_utf8_lossy(&output.stderr)
11+
)
12+
}
13+
14+
#[test]
15+
fn logs_level_filter_matches_exact_log_level() {
16+
let home = tempdir().unwrap();
17+
let cache = tempdir().unwrap();
18+
let logs_dir = cache.path().join("cortex").join("logs");
19+
fs::create_dir_all(&logs_dir).unwrap();
20+
fs::write(
21+
logs_dir.join("cortex.log"),
22+
[
23+
"2026-05-20T00:00:00Z INFO startup ok",
24+
"2026-05-20T00:00:01Z WARN contains ERROR in the message",
25+
"2026-05-20T00:00:02Z ERROR actual failure",
26+
"",
27+
]
28+
.join("\n"),
29+
)
30+
.unwrap();
31+
32+
let output = Command::new(env!("CARGO_BIN_EXE_Cortex"))
33+
.args(["logs", "--level", "error", "--lines", "10"])
34+
.env("HOME", home.path())
35+
.env("XDG_CACHE_HOME", cache.path())
36+
.env_remove("CORTEX_HOME")
37+
.output()
38+
.unwrap();
39+
40+
assert!(
41+
output.status.success(),
42+
"logs --level failed:\n{}",
43+
combined_output(&output)
44+
);
45+
46+
let stdout = String::from_utf8_lossy(&output.stdout);
47+
assert!(stdout.contains("ERROR actual failure"), "output:\n{stdout}");
48+
assert!(!stdout.contains("WARN contains ERROR"), "output:\n{stdout}");
49+
assert!(!stdout.contains("INFO startup"), "output:\n{stdout}");
50+
}
51+
52+
#[test]
53+
fn logs_level_filter_does_not_match_partial_level() {
54+
let home = tempdir().unwrap();
55+
let cache = tempdir().unwrap();
56+
let logs_dir = cache.path().join("cortex").join("logs");
57+
fs::create_dir_all(&logs_dir).unwrap();
58+
fs::write(
59+
logs_dir.join("cortex.log"),
60+
"2026-05-20T00:00:00Z ERROR actual failure\n",
61+
)
62+
.unwrap();
63+
64+
let output = Command::new(env!("CARGO_BIN_EXE_Cortex"))
65+
.args(["logs", "--level", "err", "--lines", "10"])
66+
.env("HOME", home.path())
67+
.env("XDG_CACHE_HOME", cache.path())
68+
.env_remove("CORTEX_HOME")
69+
.output()
70+
.unwrap();
71+
72+
assert!(
73+
output.status.success(),
74+
"logs --level failed:\n{}",
75+
combined_output(&output)
76+
);
77+
78+
let stdout = String::from_utf8_lossy(&output.stdout);
79+
assert!(
80+
!stdout.contains("ERROR actual failure"),
81+
"output:\n{stdout}"
82+
);
83+
}

0 commit comments

Comments
 (0)