From 58dfbcd01392296fd496a92695544fc438c06748 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 15:19:31 +0000 Subject: [PATCH] fix(log): escape control characters in log messages --- apps/soundcraft/src/logging.rs | 8 +++++++- apps/soundcraft/src/logging/tests.rs | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/soundcraft/src/logging.rs b/apps/soundcraft/src/logging.rs index a040dd08..0a90483c 100644 --- a/apps/soundcraft/src/logging.rs +++ b/apps/soundcraft/src/logging.rs @@ -166,7 +166,13 @@ pub fn rotate(dir: &Path) -> std::io::Result<(PathBuf, File)> { /// One formatted log line: ` [] : \n`. pub fn format_line(ts: &str, level: log::Level, thread: &str, target: &str, message: &str) -> String { let message = message.strip_suffix('\n').unwrap_or(message); - format!("{ts} {level:<5} [{thread}] {target}: {message}\n") + // Control characters are written escaped: a message quoting a session file (a plugin id, a + // path) must not start a line of its own in the log or steer the terminal. + let mut escaped = String::with_capacity(message.len()); + for c in message.chars() { + if c.is_control() { escaped.extend(c.escape_default()) } else { escaped.push(c) } + } + format!("{ts} {level:<5} [{thread}] {target}: {escaped}\n") } /// Where formatted lines go besides standard error: kept in memory until a file is attached, diff --git a/apps/soundcraft/src/logging/tests.rs b/apps/soundcraft/src/logging/tests.rs index 1103ba68..ebbc85a5 100644 --- a/apps/soundcraft/src/logging/tests.rs +++ b/apps/soundcraft/src/logging/tests.rs @@ -158,8 +158,15 @@ fn a_line_carries_time_level_thread_target_and_message() { format_line("2026-10-08T07:59:17.728Z", log::Level::Warn, "main", "soundcraft_mix", "eq_7band: the plugin did not accept its stored state"), "2026-10-08T07:59:17.728Z WARN [main] soundcraft_mix: eq_7band: the plugin did not accept its stored state\n" ); - // A multi-line message keeps its lines; the record still ends in exactly one newline. - assert!(format_line("t", log::Level::Error, "w", "x", "a\nb\n").ends_with("x: a\nb\n")); + // A message with breaks of its own stays one record; the record still ends in exactly one newline. + assert!( + format_line("t", log::Level::Error, "w", "x", "a\nb\n").ends_with("x: a\\nb\n"), + "a break inside the message is escaped, the final one dropped" + ); + assert!( + format_line("t", log::Level::Error, "w", "x", "plugin \u{1b}[2J\r\n2026 INFO forged") + .ends_with("x: plugin \\u{1b}[2J\\r\\n2026 INFO forged\n") + ); } #[test]