story-kit: merge 162_story_colored_server_terminal_log_output

This commit is contained in:
Dave
2026-02-24 17:43:35 +00:00
parent 37e0cf8194
commit e4ce7f7202

View File

@@ -53,6 +53,17 @@ impl LogEntry {
pub fn formatted(&self) -> String { pub fn formatted(&self) -> String {
format!("{} [{}] {}", self.timestamp, self.level.as_str(), self.message) 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 { pub struct LogBuffer {
@@ -74,8 +85,7 @@ impl LogBuffer {
timestamp, timestamp,
message, message,
}; };
let line = entry.formatted(); eprintln!("{}", entry.colored_formatted());
eprintln!("{line}");
if let Ok(mut buf) = self.entries.lock() { if let Ok(mut buf) = self.entries.lock() {
if buf.len() >= CAPACITY { if buf.len() >= CAPACITY {
buf.pop_front(); 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("info"), Some(LogLevel::Info));
assert_eq!(LogLevel::from_str_ci("DEBUG"), None); 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}"
);
}
}
} }