Files
task_pilot/docs/decisions/0004-log-encoding.md
T
CNWei dd7612bc74 feat: 编辑器实时探测并显示已关联虚拟环境
fix: 按系统 ANSI 代码页解码子进程输出,修复中文日志乱码
2026-10-24 22:30:10 +08:00

2.3 KiB
Raw Blame History

ADR-0004:运行日志的输出编码

  • 状态:已接受
  • 日期:2026-10-24

背景

任务日志原先按 UTF-8 无损方式(String::from_utf8_lossy)解码子进程输出。中文 Windows 上多数程序并不输出 UTF-8:Python 默认使用系统 ANSI 代码页(简体中文为 GBK/936),cmd.exe、部分 CLI 使用控制台代码页。结果日志里出现成片的 ����,例如 release/TaskPilot/logs/copyparty/1.log 中真实的 copyparty ������ (PID: 22056)。

同一处还有两个次要问题:读取日志按 64 KiB 切块时可能切开多字节字符;终态任务只要日志超过 64 KiB,前端拿到首块就停止轮询,导致尾部丢失。

决策

  1. 在写入侧解码:子进程每读完一行就解码为 UTF-8 再落盘,日志文件因此始终是合法 UTF-8,字节 offset 语义不变。
  2. 默认 auto:先按 UTF-8 严格解码,失败时回退到系统 ANSI 代码页(GetACP),并按代码页映射到 encoding_rs 编码(936→GBK、950→Big5、932→Shift_JIS、949→EUC-KR、125x→windows-125x)。
  3. 允许用 TASK_PILOT_LOG_ENCODING 指定编码标签(如 gbk、utf-8、windows-1251);显式指定时不再嗅探,行为完全确定。
  4. HTTP 任务响应体沿用同一解码器。
  5. 读取侧只做 UTF-8 边界对齐:块尾落在多字节字符中间时,把不完整字节留给下一次读取;只有真正非法的字节才替换为 U+FFFD。
  6. complete 改为"运行已结束且已读到文件末尾",修复长日志被截断的问题。

原因

  • 解码放在写入侧,只需处理一次,SSE、增量读取、下载、grep 得到的内容完全一致。
  • 先 UTF-8 再回退,是因为 UTF-8 合法序列有强约束,误判概率低;反过来(先 GBK)会把大量 UTF-8 日志解坏。
  • 使用系统代码页而不是硬编码 GBK,保证非中文 Windows 上的行为同样正确。
  • 逐行解码避免多字节字符被缓冲区边界切开,且不受行尾差异影响。

边界

  • 逐行启发式无法覆盖"一行内混用多种编码",也无法识别恰好构成合法 UTF-8 的 ANSI 字节序列;这两种情况需要显式设置 TASK_PILOT_LOG_ENCODING。
  • 修复前写入的历史日志已经是替换字符,不会恢复。
  • 非 Windows 平台回退编码为 UTF-8,不做代码页探测。