story-kit: merge 162_story_colored_server_terminal_log_output
This commit is contained in:
@@ -53,6 +53,17 @@ impl LogEntry {
|
||||
pub fn formatted(&self) -> String {
|
||||
format!("{} [{}] {}", self.timestamp, self.level.as_str(), self.message)
|
||||
}
|
||||
|
||||
/// Format with ANSI color codes for terminal output.
|
||||
/// WARN is yellow, ERROR is red, INFO has no color.
|
||||
fn colored_formatted(&self) -> String {
|
||||
let line = self.formatted();
|
||||
match self.level {
|
||||
LogLevel::Warn => format!("\x1b[33m{line}\x1b[0m"),
|
||||
LogLevel::Error => format!("\x1b[31m{line}\x1b[0m"),
|
||||
LogLevel::Info => line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogBuffer {
|
||||
@@ -74,8 +85,7 @@ impl LogBuffer {
|
||||
timestamp,
|
||||
message,
|
||||
};
|
||||
let line = entry.formatted();
|
||||
eprintln!("{line}");
|
||||
eprintln!("{}", entry.colored_formatted());
|
||||
if let Ok(mut buf) = self.entries.lock() {
|
||||
if buf.len() >= CAPACITY {
|
||||
buf.pop_front();
|
||||
@@ -295,4 +305,62 @@ mod tests {
|
||||
assert_eq!(LogLevel::from_str_ci("info"), Some(LogLevel::Info));
|
||||
assert_eq!(LogLevel::from_str_ci("DEBUG"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colored_formatted_warn_has_yellow_ansi() {
|
||||
let entry = LogEntry {
|
||||
level: LogLevel::Warn,
|
||||
timestamp: "2026-01-01T00:00:00Z".into(),
|
||||
message: "test warning".into(),
|
||||
};
|
||||
let colored = entry.colored_formatted();
|
||||
assert!(colored.starts_with("\x1b[33m"), "WARN should start with yellow ANSI code");
|
||||
assert!(colored.ends_with("\x1b[0m"), "WARN should end with ANSI reset");
|
||||
assert!(colored.contains("[WARN]"));
|
||||
assert!(colored.contains("test warning"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colored_formatted_error_has_red_ansi() {
|
||||
let entry = LogEntry {
|
||||
level: LogLevel::Error,
|
||||
timestamp: "2026-01-01T00:00:00Z".into(),
|
||||
message: "test error".into(),
|
||||
};
|
||||
let colored = entry.colored_formatted();
|
||||
assert!(colored.starts_with("\x1b[31m"), "ERROR should start with red ANSI code");
|
||||
assert!(colored.ends_with("\x1b[0m"), "ERROR should end with ANSI reset");
|
||||
assert!(colored.contains("[ERROR]"));
|
||||
assert!(colored.contains("test error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colored_formatted_info_has_no_ansi() {
|
||||
let entry = LogEntry {
|
||||
level: LogLevel::Info,
|
||||
timestamp: "2026-01-01T00:00:00Z".into(),
|
||||
message: "test info".into(),
|
||||
};
|
||||
let colored = entry.colored_formatted();
|
||||
assert!(!colored.contains("\x1b["), "INFO should have no ANSI escape codes");
|
||||
assert!(colored.contains("[INFO]"));
|
||||
assert!(colored.contains("test info"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_buffer_entries_have_no_ansi_codes() {
|
||||
let buf = fresh_buffer();
|
||||
buf.push_entry(LogLevel::Info, "info msg".into());
|
||||
buf.push_entry(LogLevel::Warn, "warn msg".into());
|
||||
buf.push_entry(LogLevel::Error, "error msg".into());
|
||||
|
||||
let recent = buf.get_recent(10, None, None);
|
||||
assert_eq!(recent.len(), 3);
|
||||
for line in &recent {
|
||||
assert!(
|
||||
!line.contains("\x1b["),
|
||||
"Ring buffer entry should not contain ANSI codes: {line}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user