Skip to content

feat(file_ops): 日志导出改为分卷 zip,不再截断文件#246

Open
ocsin1 wants to merge 2 commits into
MistEO:mainfrom
ocsin1:feat/log-export-split-volumes
Open

feat(file_ops): 日志导出改为分卷 zip,不再截断文件#246
ocsin1 wants to merge 2 commits into
MistEO:mainfrom
ocsin1:feat/log-export-split-volumes

Conversation

@ocsin1

@ocsin1 ocsin1 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

正文

  • 按 24.5 MB/卷切分,所有 on_error/vision 图保证完整入包
  • 图片按 mtime 新→旧排序,最近崩溃图一定在 part01.zip
  • async + spawn_blocking 解决导出期间 UI 卡顿
  • 产物移到 debug_exports/,自动保留最近 10 次
  • vision 始终包含;export_logs 去掉 save_draw 参数

Summary by Sourcery

将日志导出为多卷 zip 归档文件,提供更精确的大小控制,并自动保留最近的导出记录。

New Features:

  • 将导出的日志拆分为多个 zip 卷,每卷大小约限制在 24.5 MB 以内,同时确保没有文件被截断。
  • 在存在视觉调试图像时,总是将其包含在导出中;图像按修改时间从新到旧排序,并保证最近的崩溃图像出现在第一卷中。
  • 将导出的日志归档存储在专用的 debug_exports 目录下,并将第一卷的路径返回给调用方。

Enhancements:

  • 使用基于原子计数的 writer 替代基于互斥锁的大小跟踪方式,并通过更精确的 zip 大小估算来进行卷拆分。
  • 在主线程之外以阻塞任务的方式运行日志导出,以避免导出期间出现 UI 卡顿。
  • 自动清理较旧的导出目录,只保留最近的 10 次导出。
  • 改进在读取调试图像目录或管理导出目录时的日志记录与错误处理。
Original summary in English

Summary by Sourcery

Export logs into multi-volume zip archives with better size accounting and automatic retention of recent exports.

New Features:

  • Split exported logs into multiple zip volumes capped at approximately 24.5 MB each while ensuring no files are truncated.
  • Always include vision debug images in exports whenever they exist, with images ordered by modification time from newest to oldest and recent crash images guaranteed in the first volume.
  • Store exported log archives under a dedicated debug_exports directory and return the path of the first volume to the caller.

Enhancements:

  • Replace mutex-based size tracking with an atomic-counting writer and more accurate zip size estimation for volume splitting.
  • Run log export on a blocking task off the main thread to avoid UI freezes during export.
  • Automatically prune older export directories, keeping only the most recent 10 exports.
  • Improve logging and error handling when reading debug image directories or managing export directories.

- 按 24.5 MB/卷切分,所有 on_error/vision 图保证完整入包
- 图片按 mtime 新→旧排序,最近崩溃图一定在 part01.zip
- async + spawn_blocking 解决导出期间 UI 卡顿
- 产物移到 debug_exports/,自动保留最近 10 次
- vision 始终包含;export_logs 去掉 save_draw 参数

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些整体性的反馈:

  • estimate_entry_upper_bound 中,从 archive_name.as_bytes().len() 切换到 archive_name.len(),对于包含非 ASCII 字符的名称,会低估头部大小;建议改回按字节长度计算,以确保上界估算仍然安全。
  • prune_old_exports 在计算 let keep_others = MAX_EXPORTS_TO_KEEP - 1 时假设 MAX_EXPORTS_TO_KEEP >= 1;添加一个 debug 断言或前置返回的保护,可以让它在常量未来被修改时更健壮。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
-`estimate_entry_upper_bound` 中,从 `archive_name.as_bytes().len()` 切换到 `archive_name.len()`,对于包含非 ASCII 字符的名称,会低估头部大小;建议改回按字节长度计算,以确保上界估算仍然安全。
- `prune_old_exports` 在计算 `let keep_others = MAX_EXPORTS_TO_KEEP - 1` 时假设 `MAX_EXPORTS_TO_KEEP >= 1`;添加一个 debug 断言或前置返回的保护,可以让它在常量未来被修改时更健壮。

## Individual Comments

### Comment 1
<location path="src-tauri/src/commands/file_ops.rs" line_range="695-700" />
<code_context>

-    Ok(zip_path.to_string_lossy().to_string())
+/// 从导出目录名末尾解出 `YYYYMMDD-HHMMSS` 时间戳作为排序键。
+fn parse_export_timestamp(dir_name: &str) -> Option<String> {
+    const TS_LEN: usize = 15;
+    if dir_name.len() < TS_LEN {
+        return None;
+    }
+    let tail = &dir_name[dir_name.len() - TS_LEN..];
+    let bytes = tail.as_bytes();
+    let shape_ok = bytes[..8].iter().all(|b| b.is_ascii_digit())
</code_context>
<issue_to_address>
**issue (bug_risk):** 按字节索引切片 `dir_name` 在遇到非 ASCII 目录名时可能会 panic。

`dir_name.len()` 返回的是字节长度,因此 `&dir_name[dir_name.len() - TS_LEN..]` 可能会把非 ASCII 名称中的 UTF‑8 码点切成两半,从而在运行时触发 panic。由于你只需要 `YYYYMMDD-HHMMSS` 这个后缀,建议基于 `dir_name.as_bytes()` 操作,然后对尾部使用 `from_utf8`,或者从后往前查找 `-` 分隔符并基于字节切片做校验,这样既能避免 panic,又能保留基于时间戳的排序行为。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In estimate_entry_upper_bound, switching from archive_name.as_bytes().len() to archive_name.len() can underestimate header size for non-ASCII names; consider reverting to byte length to ensure the upper bound remains safe.
  • prune_old_exports assumes MAX_EXPORTS_TO_KEEP >= 1 when computing let keep_others = MAX_EXPORTS_TO_KEEP - 1; adding a debug assertion or early-return guard would make this more robust against future changes to the constant.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `estimate_entry_upper_bound`, switching from `archive_name.as_bytes().len()` to `archive_name.len()` can underestimate header size for non-ASCII names; consider reverting to byte length to ensure the upper bound remains safe.
- `prune_old_exports` assumes `MAX_EXPORTS_TO_KEEP >= 1` when computing `let keep_others = MAX_EXPORTS_TO_KEEP - 1`; adding a debug assertion or early-return guard would make this more robust against future changes to the constant.

## Individual Comments

### Comment 1
<location path="src-tauri/src/commands/file_ops.rs" line_range="695-700" />
<code_context>

-    Ok(zip_path.to_string_lossy().to_string())
+/// 从导出目录名末尾解出 `YYYYMMDD-HHMMSS` 时间戳作为排序键。
+fn parse_export_timestamp(dir_name: &str) -> Option<String> {
+    const TS_LEN: usize = 15;
+    if dir_name.len() < TS_LEN {
+        return None;
+    }
+    let tail = &dir_name[dir_name.len() - TS_LEN..];
+    let bytes = tail.as_bytes();
+    let shape_ok = bytes[..8].iter().all(|b| b.is_ascii_digit())
</code_context>
<issue_to_address>
**issue (bug_risk):** Slicing `dir_name` by byte index can panic for non-ASCII directory names.

`dir_name.len()` returns a byte length, so `&dir_name[dir_name.len() - TS_LEN..]` can split a UTF‑8 code point for non‑ASCII names and panic at runtime. Since you only need the `YYYYMMDD-HHMMSS` suffix, consider operating on `dir_name.as_bytes()` and then `from_utf8` the tail, or searching backward for the `-` separator and validating via byte slices to avoid panics while preserving timestamp-based sorting.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src-tauri/src/commands/file_ops.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

运行日志打包加上vision文件夹 关于mxu日志不全问题 bug

1 participant