Skip to content

fix(desktop): 日志接入审计修复:操作串联、引擎日志转发、被拒事件可见和插件日志接线 - #1945

Merged
xiaoyumuxi merged 7 commits into
previewfrom
fix/desktop-logging-1927
Nov 14, 2026
Merged

xiaoyumuxi merged 7 commits into
previewfrom
fix/desktop-logging-1927

Conversation

@xiaoyumuxi

@xiaoyumuxi xiaoyumuxi commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

关联 Issue / Linked issue

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

#1927 审计了 Viva 桌面端的日志接入:写入、查询、保留、导出这条主干是通的,但有 13 项接入问题,其中两项会直接丢日志:两条事件不在 Core 目录里,写入时被拒绝;Core 交给宿主的语言服务器、VCS、插件后端日志事实从未写进 v2 日志。另外,用户动作、Core 请求和宿主记录之间没有 op 串联,错误提示无法跳到对应记录;写入器空闲时每 500 ms 轮询一次 Core;插件的 ${logDir} 不受保留和清空约束;日志规则文档仍按已删除的 macOS 客户端描述。用户附上诊断包时,维护者看不到终端启动失败、语言服务器日志和大部分错误提示对应的记录。

本 PR 修复全部 13 项。Core 侧的目录与说明调整在 Lithe-core-rust#46,它已合入 main,本 PR 固定在合入提交 e811068。

改动 / Changes

1. 写入器:被拒事件可见、丢弃计数、按唤醒写入(#1927 第 3、8、11 项) — internal/logstore、internal/core/log_waker.go、internal/logwindow

  • 被 Core 拒绝的事件按事件 ID 和错误码保留最近 20 种,日志窗口状态栏和 collection-status.txt 显示数量与最近一条(新词条 desktop.logs.rejected);不再算作写入失败。
  • 整批编码失败的事件计入 dropped,由下一次写入的 log.dropped 报告;批内的 log.dropped 丢失时转交下一次,不重复计数。
  • 删除 500 ms 的 kernelTick 轮询:Core 记录事实时经 lithe_core_install_log_waker 回调安排一次 100 ms 合并写入;详细诊断由写入器的一次性定时器在 15 分钟后关闭;关闭写入器时先移除回调再做最后一次写入。只有应用主进程调用 ObserveCoreFacts。
  • 自定义日志目录不可用、回退到默认目录时记录 app.logging.redirect_failed;读不出 logging.json 记录 app.settings.decode_failed;推送 DEBUG 开关失败记录一次 app.logging.diagnostics_failed。
  • 日志窗口不再把所有失败都记成 app.logging.diagnostics_failed:用户操作失败记 app.notification.operation_failed,窗口自己的读取和保存失败记 app.background.operation_failed。
  • ErrorFields 移到 core.LogErrorFields,各处共用。

2. 目录一致性测试与 surface 常量(第 1、5 项) — internal/core/log_sites_test.go

  • 新增 TestEveryLoggedEventIsAcceptedByTheKernel:从源码读出每个生产日志调用的级别、事件 ID 和字段,交给当前链接的内核按目录编码;拼错的 ID、与目录不同的级别、缺少或多出的字段都在测试里失败。
  • surface 改为 actionSurface 常量,测试要求常量集合与目录描述完全一致、每个调用解析出的 surface 都在集合内,并禁止把字符串转换成该类型。
  • 按入口修正 surface:右键菜单、拖放(drag_drop,Core fix(macos): return to welcome screen when closing project #46 新增)、工具栏、按钮分别记录;Find Action 与快捷键运行同一命令时由命令自己带上来源(runFrom);构建、运行、调试的共享启动路径不再直接记录,由按钮、菜单和插件请求各自记录。
  • 终端启动失败改用目录里的 terminal.session.start_failed,VCS 提供者冲突改用 vcs.provider.skipped,草稿恢复记 editor.recovery.applied,启动残留进程处理记 app.startup.leftovers_resolved(这些事件由 Core fix(macos): return to welcome screen when closing project #46 声明)。

3. 操作链(第 4 项) — internal/core/origin.go、internal/workbench/operation.go、notifications.go

  • 每个用户动作分配一个 op(action-N),后台工作通过 a.background 继承来源;Core 请求带 origin { source, trigger, operation },自动刷新的请求标为 background。宿主记录和失败提示都带同一个 op。
  • 失败提示带“查看日志”按钮,按 op 打开日志窗口并过滤到这次动作的记录。

4. 补缺失记录、拆分通用失败事件(第 6、7、10 项)

  • 逐个检查 notifyError:用户可见失败补 operationFailed;不需要记录的按四类例外写 // log-exempt: <类别>:<原因>。例如拖动移动文件失败、在新窗口打开工作树失败、最近项目读取失败都已记录。
  • 设置文件读不出时记录 app.settings.decode_failed(stage=settings)。读不出后被默认值覆盖的问题不是日志问题,另开 [Bug] 设置文件读不出时以默认值启动,下一次保存覆盖用户原有的全部设置 #1943。
  • app.notification.operation_failed 只用于用户动作失败(ERROR);后台失败和降级改记 app.background.operation_failed(WARN),例如语言服务器选择的警告、搜索清理、后台列目录;被新一次加载取代的构建模型记 plugin_model_superseded(WARN)。
  • 目录里只有旧客户端使用的 90 个宿主事件由 Core fix(macos): return to welcome screen when closing project #46 删除。

5. 插件日志接线(第 2、9、12 项) — internal/pluginbackend/trace.go、internal/core/plugin_operation.go、internal/pluginservice

  • 插件后端会话的 lithe/log 和引擎日志事实(带 event、catalogLevel、fields)转发给写入器,保留 Core 给的通道;语言服务器 stderr 在 Core 侧已按每秒 200 行限流。
  • 详细诊断开关变化(包括 15 分钟到期)同步调用 lsp.setDetailedDiagnostics,上游语言服务器的 trace 随之开关。
  • 插件 ${logDir} 改为 Core 返回的会话内插件文件目录(sessions/<会话>/plugins/<id>/files,0700),纳入保留限额和“清空日志”。
  • 宿主代记声明式贡献(diagnostics.recordPluginOperation):
    • languageServer/start 启动前的失败由宿主记录,之后通过 startupOperation 交给 Core。
    • debugAdapter/start:stdio 和 hosted 适配器从解析到应答 initialize,按 resolve/launch/timeout/exit/initialize 记录;用户停止和 Core 拒绝不记录。
    • lithe/build/model、lithe/build/dependencies 的命令式来源按 plan/resolve/launch/exit/decode/timeout 记录;provider 请求由插件 SDK 自己记录;项目关闭取消的加载不记录。
    • Core 拒绝代记时改记 app.background.operation_failed(stage=plugin_operation_record)。

6. 构建脚本:切换 Core 检出时重新编译工作区成员 — scripts/desktop/build-desktop-core.sh

  • 固定检出和 LITHE_CORE_RUST_DIR 共用一个 Cargo 编译目录。cargo 按成员在工作区内的相对路径计算哈希,两个检出编译到同一组文件、只按修改时间判断新旧,切回固定检出后链接的仍是另一个检出的事件目录。现在脚本按 target 和 profile 记录上次编译工作区成员的检出,换检出时先 cargo clean --workspace(registry 依赖仍走缓存),检出没变就不清理。同步更新 docs/ci-builds.md 和 scripts/resources/worktree-resources.json 的说明。
  • PINNED_CORE_REVISION 固定到 Core main 上 fix(macos): return to welcome screen when closing project #46 的合入提交 e811068(与原分支提交 6f2d185 的树相同,都是 d439ba0)。

7. 规则文档(第 13 项)

  • .agents/skills/develop-lithe/references/logging.md 整体重写为 Viva/Go:必须记录的情形与 Go 写法、四类例外、设计步骤和反例、只走目录、写入器与入口表、一条终态、op 和异步边界、Core 请求轨迹、插件代记、DEBUG 门与唤醒、字段与隐私、提示跳转日志、门禁现状、验证和检查清单。
  • SKILL.md 规则 7 和“显式处理失败”改为 Go 的丢弃写法与目录常量要求。

8. 顺带修复:preview 上界面文案检查失败 — internal/workbench/shell_view.go

  • fix(desktop): 空编辑区快捷键提示按当前平台与 Keymap 生成 #1936 合入后,node scripts/i18n/verify-app-language-localization.mjs 把空编辑区提示里的 Keymap.Label("search.everywhere") 当成硬编码文案,preview 的 Desktop CI 在 “Verify shared localization” 失败。现在把提示改成结构体表(文案、未绑定手势、命令 ID),不放宽脚本,行为不变。

9. 其他小改动

  • internal/webpreview 去掉直接使用 log/slog 的输出。
  • workbench.toolWindow.hide 的 surface 改为 context_menu。
  • shared/platform-feature-matrix/features/diagnostics-export.json:证据和说明改为本 PR 之后的 Viva 实现,验证状态保持 pending。

不在本 PR 范围内 / Out of scope


类型专属:fix

复现与根因

检查项


范围检查 / Scope checks

功能矩阵 / Feature matrix

  • 已更新 shared/platform-feature-matrix/features/diagnostics-export.json:两端的实现状态和验证状态、证据路径、owner、验证动作都如实填写;没有运行验证的保持 pending
  • 已运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh;改了平台实现路径的,还运行了 ./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh origin/preview HEAD
  • 没有提交 .artifacts/ 下生成的矩阵视图
  • [N/A] 没有更新矩阵的,理由:已更新

插件归属 / Plugin ownership

  • 已在仓库根目录运行 develop-lithe 第三节“插件归属与宿主零依赖”的自查命令,结果为空
  • 新增或修改的测试只使用测试内联的合成插件、Core 契约 fixture 或插件市场索引快照;没有读取具体插件的插件包、仓库检出、构建产物或清单,没有使用真实插件 ID,没有“缺少插件时跳过”的测试
  • 没有为具体插件提供的能力新建或更新宿主功能矩阵条目;宿主条目的证据和验证动作不引用插件的测试、插件包或插件仓库
  • 插件文案没有写进 shared/i18n/;插件的构建步骤、资源路径和补丁没有写进 scripts/resources/、docs/ci-builds.md、Agent Note 或 Skill
  • [N/A] 修改了通用扩展点、可能影响已发布插件的,已在受影响插件的仓库运行它的测试:没有改插件契约或宿主方法;插件看到的变化只有 ${logDir} 指向会话内目录,插件只能把它当不透明路径使用

统一日志系统 / Structured logging

界面文案 / i18n

  • 宿主文案都走共享 i18n:在 shared/i18n/ 补齐中英词条,运行 node scripts/i18n/generate-i18n.mjs 并提交生成结果,没有手改生成文件;Viva 代码通过 desktop/internal/i18n/ 的接口读取
  • 插件提供的文字(清单标题和说明、插件界面、插件后端消息)由插件自己翻译,没有写进 shared/i18n/
  • 已运行 node scripts/i18n/verify-app-language-localization.mjs 和 node scripts/i18n/generate-i18n.mjs --check,没有放宽脚本或扩大豁免名单

逻辑归属 / Logic placement

  • 可共享的逻辑在 Rust Core:事件目录、字段校验、通道推导、请求轨迹和插件操作记录都在 Core;桌面端只做写入、op 分配和接线
  • [N/A] 修改平台层已有的可共享逻辑时已迁入 Core:本 PR 改的写入器、op 分配和接线都是平台 I/O;目录校验、字段编码和通道推导原本就在 Core
  • 工具或运行时专属的知识放在插件或成熟上游,宿主和 Core 没有按工具名加分支

性能与内存 / Performance and memory

  • 功能、子进程、语言服务器、WebView 首次需要才启动;没有新增空闲轮询(删除了写入器 500 ms 的空闲轮询);缓存和缓冲有上限(被拒事件只保留最近 20 种);关闭项目、窗口、面板、标签时释放独占资源
  • 主线程没有文件读写、目录遍历、进程启动;跨 C ABI 只传批量事件
  • [N/A] 性能基线:不涉及启动、打开项目、输入、滚动、大文件、长输出或搜索

设置 / Settings

N/A:没有新增或修改设置项和设置页。

插件契约 / Plugin contract

N/A:不改插件契约、能力版本、插件函数协议或宿主方法;Core #46 只改日志目录和说明。

发行包与资源 / Bundle and resources

  • 运行时不向已安装的 app bundle 或 Windows 安装目录写入;插件日志目录在会话日志目录下(0700),并运行 ./scripts/desktop/verify/verify-runtime-bundle-immutability.sh
  • 新增或修改宿主自身的下载、解压、生成或缓存资源的,已同步 scripts/resources/worktree-resources.json、scripts/resources/reuse-worktree-resources.mjs 的校验路由、对应测试和 docs/ci-builds.md“独立工作树的本地编译”一节(改动 6 只改变 Cargo 编译目录的清理时机,更新了说明,校验路由无需变化)

无用代码与重构 / Dead code and refactoring


验证 / Verification

命令或操作 平台 结果
LITHE_CORE_RUST_DIR=<Core #46 检出> scripts/desktop/desktop-go.sh test -race ./...(rebase 到 preview 295ee28 之前) macOS arm64 通过
rebase 后 gofmt -l、desktop-go.sh vet ./... macOS arm64 无输出,通过
rebase 后 desktop-go.sh test -count=1 ./internal/core/ ./internal/workbench/ ./internal/logstore/ macOS arm64 通过
.agents/skills/write-stable-tests/scripts/test-stability-macos.sh,本 PR 新增和改动的测试 macOS arm64 68 个匹配:67 通过,1 个跳过(TestBuildCommandRunner 是被测子进程入口);两个超过 1 s 警告线:TestEveryLoggedEventIsAcceptedByTheKernel 1310 ms(解析全部生产源码并逐条交给内核编码),TestBuildCommandModelLoadIsRecordedAtTheStageItEnded 1070 ms(启动 5 次真实子进程);均低于 15 s 上限
node scripts/i18n/verify-app-language-localization.mjs、node scripts/i18n/generate-i18n.mjs --check — 通过,11217/11217
./scripts/agent-notes/verify-agent-notes.sh — 通过
./scripts/desktop/verify/verify-runtime-bundle-immutability.sh macOS arm64 通过
./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh、verify-platform-feature-matrix-change.sh origin/preview HEAD — 通过
develop-lithe 第三节插件归属自查命令 — 结果为空
Core #46:cargo test --workspace、generate-log-events.mjs --check、clippy、verify-docs.mjs macOS arm64 通过(见 Core PR)

未能执行的检查和原因:

  • 没有启动桌面应用做整机验收:失败提示的“查看日志”跳转、日志窗口状态栏的被拒计数、详细诊断 15 分钟到期,都只由组件测试覆盖;界面截图(明暗主题、中英文)也没有附。
  • 没有在 Windows 实机或 Parallels 上运行。
  • 固定修订已指向 Core main 上的 e811068。标记 ready 前还缺整机界面检查和 PR 截图(develop-lithe 第五节)。

提交前确认 / Final checks

  • 分支从最新的 preview 创建;目标分支:feat、fix、docs 为 preview,release 以发布流程要求为准
  • 以 Draft 提交,本地验证通过后才标记 ready
  • 没有无关改动,没有提交 .build/、target/、dist/、.artifacts/ 等产物,没有凭据、个人路径和未脱敏的日志或截图
  • 本次构建、测试或预览启动的 Lithe 应用和子进程已全部关闭,没有进程残留

- 被 Core 拒绝的事件按事件 ID 和错误码记录(最近 20 种),日志窗口状态栏和
  collection-status.txt 显示数量与最近一条;不再算作写入失败。
- 整批编码失败的事件计入 dropped,由下一次写入的 log.dropped 报告;批内的
  log.dropped 丢失时转交下一次报告,不重复计数。
- 去掉 500ms 轮询:Core 记录事实时通过 lithe_core_install_log_waker 回调安排一次
  100ms 合并写入;详细诊断到期由写入器的一次性定时器关闭 Core 的 DEBUG 开关;
  关闭时先移除回调再做最后一次写入。只有应用主进程的日志调用 ObserveCoreFacts。
- 自定义日志目录不可用、回退到默认目录时记录 app.logging.redirect_failed;读不出
  logging.json 时记录 app.settings.decode_failed;推送 DEBUG 开关失败记录一次
  app.logging.diagnostics_failed。
- 日志窗口不再把所有失败记成 app.logging.diagnostics_failed:用户操作失败记
  app.notification.operation_failed,窗口自己的读取和保存失败记
  app.background.operation_failed,都带 operation。
- ErrorFields 移到 core.LogErrorFields,供各处共用。
固定检出和 LITHE_CORE_RUST_DIR 共用 Cargo 编译目录,而 cargo 按成员在工作区内的相对路径
计算哈希,两个检出把 lithe-diagnostics 等成员编译到同一组文件、同一份指纹,只按修改时间判
新旧。切回原检出时 identity.txt 写着 pinned,链接的却是 override 的事件目录。

构建脚本按 target 和 profile 记录上次编译工作区成员的检出,换了检出先 cargo clean
--workspace,registry 依赖仍走缓存;无变化的运行不清理。同步更新 ci-builds.md 和
worktree-resources.json 的说明。
新增 internal/core 的站点测试:从源码读出每个生产日志调用的级别、事件 id 和字段,交给当前
链接的内核按目录编码,拼错的 id、与目录不同的级别、缺少或多出的字段都在测试里失败,而不是
运行时被写入器拒绝。用户操作的 surface 改为 actionSurface 常量,测试要求常量集合与目录描述
完全一致、每个调用解析出的 surface 都在集合内,并禁止把字符串转换成该类型。

按入口修正 surface:右键菜单、拖放、工具栏、按钮分别记录;Find Action 与快捷键运行同一命令时
由命令自己带上来源(runFrom),打开设置、项目、查找等不再一律记成 menu;构建、运行、调试的
共享启动路径不再直接 emit,由按钮、菜单和插件请求各自记录。草稿恢复改记
editor.recovery.applied,后台失败、启动残留处理、VCS 提供者跳过等改用目录中声明的事件。
操作链(#1927 第 1–4 项):
- 每个用户动作分配一个 op(action-N),后台工作通过 a.background 继承 origin,
  Core 请求带 origin(source、trigger、operation),宿主记录和失败提示都带同一个 op。
- 失败提示带“查看日志”按钮,按 op 打开日志窗口并过滤到这次动作的记录。
- 补齐缺失的失败记录;app.notification.operation_failed 只用于用户动作失败,
  后台失败改记 app.background.operation_failed,被取代的模型加载记为
  plugin_model_superseded。

插件日志(#1927 第 5–9 项):
- 插件 ${logDir} 改为会话内的 PluginFilesPath,不再推导安装目录。
- 插件后端的 lithe/log 引擎事实转发给 Core;按设置开关
  lsp.setDetailedDiagnostics,诊断详情不再只停在宿主内存。
- languageServer/start 启动前失败由宿主代记,之后通过 startupOperation 交给 Core。

宿主代记的声明式贡献(diagnostics.recordPluginOperation):
- debugAdapter/start:stdio 和 hosted 适配器从解析到应答 initialize 的结果,
  按 resolve/launch/timeout/exit/initialize 记录;用户停止和 Core 拒绝不记录;
  写入超时记为 timeout,stdio 适配器停止读取输入记为 exit。
- lithe/build/model、lithe/build/dependencies:命令式来源按
  plan/resolve/launch/exit/decode/timeout 记录;provider 请求由插件 SDK 自己记录;
  项目关闭取消的加载不记录。
- Core 拒绝记录时改记 app.background.operation_failed(plugin_operation_record)。

测试用真实 Core 和复制的测试二进制端到端验证各阶段,录制内核放到
plugin_operation_record_test.go 供调试和构建测试共用。
- references/logging.md 整体改写为 Go/Viva 桌面端的记录规则、入口和校验
- SKILL.md 规则 7 与显式处理失败改为 Go 写法
- build-desktop-core.sh 固定到 Core #46 分支提交并说明合入后迁移
- 补齐 log-exempt 标记,webpreview 去掉 slog
- 空编辑区快捷键提示改为结构体表,避免 i18n 检查把命令 ID 误判为界面文案
diagnostics-export 的证据补上唤醒回调、origin、插件代记、引擎事实转发、操作串联和目录校验测试,
说明改为 #1927 之后的实现;整机与 Windows 实机验收仍为 pending。
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 14, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 18:13
@xiaoyumuxi
xiaoyumuxi merged commit dacbce6 into preview Nov 14, 2026
2 of 6 checks passed
@lithe-code-review

lithe-code-review Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Lithe Review

结论: ❓ 审查未完成

已由服务器取消。可以重新评论 @lithe review 召唤。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant