From 9722b42692e7e8e538e9ede8ec5cdd715ff0e79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 6 Oct 2026 18:51:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E7=BA=A2=E4=B9=A6=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=97=A5=E5=BF=97=E6=8C=89=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E6=95=B0=E6=88=AA=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - commands.rs 的 log_failure 落盘前经 bounded_for_log 截到 512 字符,CommandFailed 的 4KB 脚本输出不再整段写进 AppData 日志与去重表 - 新增 bounded_for_log 的短文本与中文边界用例 --- .../src/export/draft/xhs_minitool/commands.rs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs index beb4b8257..9e17d5972 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs @@ -151,8 +151,15 @@ fn report(command: &'static str, project_path: &str, resu } } +/// 一条失败日志最多记多少个字符。 +/// +/// `CommandFailed` 的 `Debug` 里带着最多 4,000 字符的脚本输出尾部,原样落进 AppData 日志会把一次 +/// 失败撑成好几 KB,去重表也跟着留一份长字符串。日志只需要认得出「哪个变体、哪条命令失败」, +/// 完整现场已经结构化回给前端了。 +const MAX_LOGGED_FAILURE_CHARS: usize = 512; + fn log_failure(command: &'static str, project_path: &str, error: &impl std::fmt::Debug) { - let message = format!("{error:?}"); + let message = bounded_for_log(&format!("{error:?}"), MAX_LOGGED_FAILURE_CHARS); if let Ok(mut last) = LAST_FAILURE.lock() { if last.get(command).is_some_and(|seen| *seen == message) { return; @@ -162,6 +169,15 @@ fn log_failure(command: &'static str, project_path: &str, error: &impl std::fmt: app_log!("export.{command}.failed project={project_path}: {message}"); } +/// 按**字符**(不是字节)截断日志文本,避免切坏 UTF-8;发生截断时补一个省略号说明。 +fn bounded_for_log(message: &str, max_chars: usize) -> String { + if message.chars().count() <= max_chars { + return message.to_string(); + } + let head: String = message.chars().take(max_chars).collect(); + format!("{head}…") +} + fn clear_failure(command: &'static str) { if let Ok(mut last) = LAST_FAILURE.lock() { last.remove(command); @@ -176,7 +192,7 @@ fn short(hash: &str) -> &str { #[cfg(test)] mod tests { - use super::short; + use super::{bounded_for_log, short}; #[test] fn short_keeps_first_eight_characters() { @@ -184,4 +200,11 @@ mod tests { assert_eq!(short("0123"), "0123"); assert_eq!(short(""), ""); } + + #[test] + fn bounded_for_log_keeps_short_text_and_truncates_on_char_boundaries() { + assert_eq!(bounded_for_log("构建失败", 8), "构建失败"); + // 按字符截断:4 个中文字符不会被切坏,末尾补省略号。 + assert_eq!(bounded_for_log("一二三四五六", 4), "一二三四…"); + } }