Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ languageID 对应描述元数据已有的 `languageServerLaunch.executableNames`
运行投影、日志选择及订阅必须保留;不能清空被 `removeDuplicates` 去重的共享投影后,
指望日志或无变化的状态通知重新填回。

“查看日志”打开或聚焦普通编辑器标签页中的 LSP 日志,不新增底部工具窗口。
日志标签复用混合标签顺序、拖动分组、选择和关闭机制;切换服务器仍使用同一标签。它直接观察现有的最多 100 条会话事件
和服务器输出,按提供方与最低级别筛选;日志更新不经整个 AppModel 广播。
“清空视图”只记住所选提供方当前可见缓冲条目的身份,不删除持久诊断日志,
也不影响其他提供方。标记随缓冲淘汰,不建立另一个无界日志存储。
“查看日志”打开或聚焦编辑器区的“LSP 日志”标签页,不新增底部工具窗口,也不另开窗口。
每个工作区窗口只有一个这样的标签页,切换服务器仍在同一标签页里完成;菜单底部的“查看日志”打开列表中的第一个服务器。
标签页读取已脱敏的 v2 磁盘日志和实时广播,一次只显示一个服务器的通道,没有“全部语言服务器”选项;
通道由 Core 解析,Swift 不用 providerID 拼通道名。“清空视图”只隐藏所选服务器已经显示的记录,
每个服务器各有自己的清空点,不删除持久诊断日志,也不建立另一份日志存储。标签页与应用日志窗口的关系见
[应用级日志窗口与诊断导出](../feature/2026-11-03-structured-logging-log-window.md)。

正确做法是按当前会话事件显示真实内容。不要在 Swift 再实现 JSON-RPC(语言服务器
使用的请求响应协议)抓包器,或把现有文本日志复制成 Server Trace、RPC Messages
三个假通道。当前 Core 没有提供原始跟踪/消息采集,日志标签的工具栏明确提示不可用;
三个假通道。当前 Core 没有提供原始跟踪/消息采集,LSP 日志标签页的 ⓘ 说明明确写出不可用;
将来确需采集时,应在 Core 既有协议所有者处设计显式启用、脱敏和预算,再扩展契约。
本次没有新缓存目录、下载或安装资源写入,运行时只读发行基线不变。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ Git 的工作区、工作树、目录、分支、历史提交和 Stash/Shelf 比
关闭或移走最后一个标签后合并空组;新标签进入当前聚焦的组。文件和会话生命周期不交给布局树。
分隔器继续使用 `LitheSplitPaneView`,尺寸变化留在局部视图,只在释放后提交比例。
分组是当前项目会话状态,本次不增加重启恢复编辑分屏或跨窗口传输。
LSP 日志同样是普通编辑器标签,不登记底部工具窗口。单个工作区只打开一个日志标签,
再次查看另一服务器时复用标签并更新筛选;标签可参与重排和分组。日志不拥有文件缓冲区,
选择它应清除活动文件/媒体/终端投影,关闭后通过混合标签选择恢复原内容,不能关闭未保存文件。
文件通过功能模型直接打开时,也必须让日志标签让出活动选择;工作区重置移除日志标签。
LSP 日志是普通编辑器标签(`EditorTabItem.languageLogs`),不登记底部工具窗口。单个工作区只有一个 LSP 日志标签,
再次查看另一个服务器时复用这个标签并切换服务器;标签参与重排和分组。日志标签不拥有文件缓冲区:
选中它要清除活动文件、媒体和终端投影,关闭后通过混合标签选择恢复原来的内容,不能关闭未保存的文件。
文件通过功能模型直接打开时,日志标签也必须让出活动选择;工作区重置会移除日志标签并停止它的查询。
与数据库 PR #17 集成后,数据库标签继续使用其共享工作区和查询状态,统一保留在同一个编辑分组,只允许组内重排;不能直接开放跨组移动,否则两个分组会显示同一数据库上下文。数据库工具窗口及 Services 工具窗口仍支持停靠分组。

### 终端标签使用独立的 AppKit 拖放
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## 先说结论

macOS 只保留一个看日志的地方:用 ⌘⇧L 打开的独立日志窗口,所有项目共用。窗口读取磁盘上已经由 Core 脱敏的 v2 日志,以及写入器的实时广播。过滤语法、重复折叠、事件分布、调用链和复制格式都由 Rust Core 计算,SwiftUI 只负责读文件、订阅和画表格。导出对话框和窗口使用同一套 Core 查询,导出的永远是脱敏版。语言服务的日志编辑器标签页、100 条内存日志和设置页里的日志时间线已经删除。Windows 仍然使用 v1 日志,没有迁移。
macOS 的应用日志只在一个地方看:用 ⌘⇧L 打开的独立日志窗口,所有项目共用。语言服务状态里的“查看日志”打开编辑器区的“LSP 日志”标签页,读的是同一份脱敏记录,一次只显示一个服务器的通道,不会改动应用日志窗口的筛选。窗口读取磁盘上已经由 Core 脱敏的 v2 日志,以及写入器的实时广播。过滤语法、重复折叠、事件分布、调用链和复制格式都由 Rust Core 计算,SwiftUI 只负责读文件、订阅和画表格。导出对话框和窗口使用同一套 Core 查询,导出的永远是脱敏版。语言服务原来那份 100 条的内存日志和设置页里的日志时间线已经删除。Windows 仍然使用 v1 日志,没有迁移。

## 问题

Expand All @@ -23,12 +23,34 @@ macOS 只保留一个看日志的地方:用 ⌘⇧L 打开的独立日志窗
- 工具栏齿轮菜单的“日志与诊断…”,紧跟在“插件…”下面,和“主题…”之间隔一条分隔线;
- 菜单栏的“日志”菜单项;
- 可改键的命令 `show-logs`,默认 ⌘⇧L;
- 语言服务状态里的“查看日志”;
- 应用内提示上的“查看日志”;
- 应用菜单的“导出诊断包…”:打开窗口后直接弹出导出对话框。

窗口已经打开时,请求只套用过滤条件并把窗口提到前面,不会再开一个窗口。

### 语言服务器日志在编辑器标签页里看

语言服务状态菜单里的“查看日志”不发 `LogWindowOpenRequest`,而是打开编辑器区的“LSP 日志”标签页(`EditorTabItem.languageLogs`)。
每个工作区窗口最多有一个这样的标签页;已经打开时,再次“查看日志”只切换服务器并聚焦这个标签页。

原因是两类用户目的不同。查语言服务器的人要一边看代码,一边连续阅读某个服务器的会话事件、stderr(标准错误输出)和服务器消息,
编辑器标签页正适合这种用法,也是用户原来查看 LSP 日志的位置。查应用问题的人在应用日志窗口里按通道、级别和时间筛选。
如果语言服务器日志借用应用日志窗口,点一次“查看日志”就会覆盖用户刚设好的通道筛选;如果单独开一个窗口,阅读日志时就离开了编辑区,用户要在窗口之间来回切换。

正确做法:

- `AppModel.showLanguageServerLogs(providerID:)` 调用 `LanguageServerLogsModel.open(providerID:)`,把 `.languageLogs` 放进混合标签顺序并聚焦它。状态菜单里某个服务器的“查看日志”传该服务器的 id;菜单底部的“查看日志”传列表中的第一个服务器。
- `LanguageServerLogsModel` 持有一个不折叠重复记录的 `LogWindowModel`。选中服务器后,先用 Core 的 `windowEngineChannel(providerID:)` 把 id 换成该服务器的通道,拿到通道后才开始查询。因此标签页不会显示未过滤的整个 `lsp` 前缀,也没有“全部语言服务器”选项。
- 标签页顶部从左到右是:服务器选择、“服务器日志”和 ⓘ 说明、最低级别(信息、警告、错误,默认信息)、“清空视图”。
- “清空视图”只隐藏当前服务器已经显示的记录,不删除磁盘上的日志。模型为每个服务器分别保存清空点(每个日志会话已显示到的 seq),切换服务器时换入该服务器自己的清空点。所以清空 Java 后切到 Go,Go 的记录照常显示;切回 Java,之前清空的记录仍然隐藏,只显示清空之后的新记录。
- 生命周期属于标签页。关闭标签页或重置工作区时调用 `stop()`,查询、订阅和清空点一起丢弃;切到别的标签页只是视图消失,查询继续,清空点保留。

数据来源仍是 `MacLogStore` 的磁盘 v2 记录和实时广播,所以标签页里看到的就是应用日志窗口和诊断导出里的同一份脱敏证据。

已知限制:通道只由服务器 id 决定,不区分工作区窗口,同一个服务器在其他窗口产生的记录也会出现在这里。Server Trace 和 RPC 消息捕获没有接入 v2 日志,ⓘ 说明里写明了这一点。

不要这样做:为语言服务器另建内存缓冲或第二份日志文件;在 Swift 里用 providerID 拼通道名;为了打开这个标签页改写应用日志窗口的查询;在通道解析完成前就开始查询;用一个共享的清空点同时清空所有服务器。

### 设置里没有日志页

日志与诊断是独立界面,不和设置共用。原来的“设置 → 工具 → 日志与诊断”页已经删除,`SettingsCategory` 里没有日志分类。那一页独有的三项搬进了窗口:
Expand Down Expand Up @@ -127,19 +149,44 @@ Swift 只负责读日志文件尾部、订阅 `MacLogStore` 的实时批次、
- 显示缓存和逐像素滚动锚点由 `NSTableView` 的 coordinator 持有;详情面板高度用共享分隔条;关闭窗口时取消查询、调用链和订阅。
- Core 的脱敏占位符是 `<redacted>`,窗口灰色显示、不可点击。

### 窗口尺寸、筛选与实时呈现

表格把窗口多出来的宽度分给事件、消息和字段列;窄窗口保留原来的可读列宽及横向滚动。
自动分配只属于当前视口,不写用户偏好。只有表头拖动结束后才保存手动列宽,避免窗口放大、
隐藏列或延迟的原生列宽通知污染已保存配置。详情与调用链以视口宽度作为最小内容宽度,
从左上角展开,长内容继续横向滚动;右侧动作说明在自己的滚动区域内,不能覆盖底部状态栏。

筛选框复用共享搜索组件,ⓘ 说明表达式的 AND、排除、字段、前缀和正则语法,以及它与通道、
级别和时间条件的交集。通道列表通过共享列表的原生渲染入口承载 NSTableView(系统表格控件)的选择流程,⌘ 点击增减、Shift 点击
连续选择,“全部通道”清除限制;不要在 SwiftUI Button 的动作闭包里读取可能已变化的 currentEvent。
⌘F 由所属窗口的原生目标处理,工作台快捷键分发器先交给它,避免编辑器抢走日志窗口的 Find。
窗口关闭时目标取消事件监听;有 sheet 或输入法正在组字时保留输入所有权。

事件分布显示带秒数的时间刻度。时间范围弹层使用共享下拉承载系统日历与时分秒编辑器,
编辑草稿后点击应用才交给现有 Core 时间查询;界面使用本地时区,结束时间包含该秒的全部记录。
范围与实时批次、其他过滤条件取交集,清除后恢复不限时间。调用链的 ⓘ 明确条形和 +N ms
表示相对首条记录的偏移,不能把它解释成事件执行耗时。

实时批次只替换表格受影响的末尾行,选择、暂停或状态变化不整表重建;跟随时用短时原生滚动
衔接批次,并遵守系统减少动态效果设置。历史查询一次显示实际结果,真实突发仍保留原始时间
和顺序,不增加空闲定时器或延迟队列来制造稳定的事件发生速度。

## 考虑过的备选方案

- 保留编辑器标签页和设置页时间线两套浏览:来源和生命周期继续不同,无法回答“导出的是否就是看到的”。
- 保留旧 LSP 日志标签页的 100 条内存缓冲和设置页时间线两套浏览:来源和生命周期继续不同,无法回答“导出的是否就是看到的”。
- 设置里保留一个只放日志目录、保留与清理和详细诊断开关的“日志存储”页:日志功能又会分散在两个地方,用户要在设置和窗口之间来回找。窗口工具栏的“⋯”菜单已经能放下这几项。
- 在 Swift 里实现过滤、折叠和复制格式:开发更快,但 Windows 迁移要重写,两端行为会漂移。
- 提示里保存 seq 定位记录:显示提示时记录可能还没写盘,seq 未分配;改用通道、事件和 op 组成的引用。
- 导出提供“包含原文”选项:磁盘上根本没有原文。要支持就得另存一份未脱敏日志,扩大隐私面。
- 每个事件单独调用 Core、每次打开文件:实现简单,但跨语言调用和磁盘开销随日志量线性增长;选择按批查询和读取尾部。
- 两端一次迁移:扩大验证范围并阻塞 macOS;Windows 保留 v1 兼容,以后单独迁移。
- 语言服务器的“查看日志”继续打开应用日志窗口并切换通道(PR4 的做法):入口少一个,但每次打开都会覆盖应用日志窗口的通道筛选,表格视图也不适合连续阅读服务器输出。
- 语言服务器日志单独开一个“语言服务器日志”窗口(#1700 的早期版本):不再干扰应用日志窗口,但阅读日志时离开了编辑区,用户要在窗口之间切换,也不是用户原来查看 LSP 日志的位置。最终改回编辑器标签页,数据来源不变。
- 标签页提供“全部语言服务器”:多个服务器的输出交错在一起,连续阅读时分不清来源。需要对照多个服务器时,用应用日志窗口筛选 `lsp` 通道。

## 后果

收益:用户只有一个查看入口,看到的、复制的和导出的是同一份脱敏证据;错误提示可以一键跳到对应记录;过滤和折叠在 Core 里,有 fixture 覆盖,Windows 可以复用。
收益:应用日志只有一个查看入口,LSP 日志标签页也读同一份数据,看到的、复制的和导出的是同一份脱敏证据;错误提示可以一键跳到对应记录;过滤和折叠在 Core 里,有 fixture 覆盖,Windows 可以复用。

代价:Core 的诊断 API 变大,每个新字段都要同步契约、fixture 和 Swift 桥接。实时跟随每个批次都要调用一次 Core 查询,事件分布也由 Core 对有界窗口重新计算;高吞吐下的成本还需要整机测量,如果不可接受,应扩展 Core 的增量摘要接口,而不是在 Swift 里复制统计规则。门禁零额度意味着每个新的 `try?`、空 `catch` 或丢弃结果都必须当场记录或写明例外类别。

Expand All @@ -151,7 +198,7 @@ Swift 只负责读日志文件尾部、订阅 `MacLogStore` 的实时批次、
- `node scripts/logging/verify-log-usage.mjs`
- `node --test --test-timeout=60000 scripts/logging/test-log-events.mjs`
- `cargo test --manifest-path rust/lithe-core/Cargo.toml diagnostics`
- `./scripts/macos/test/test-macos.sh --filter 'LitheTests.(DiagnosticsWorkflowTests|LogWindowTests)'`
- `./scripts/macos/test/test-macos.sh --filter 'LitheTests.(DiagnosticsWorkflowTests|LogWindowTests|LogWindowReviewTests)'`
- `./scripts/macos/probe/probe-macos-logging-ui.sh --skip-build`
- `./scripts/architecture/verify-service-boundaries.sh`
- `./scripts/architecture/verify-shared-contracts.sh`
Expand All @@ -167,6 +214,8 @@ Swift 只负责读日志文件尾部、订阅 `MacLogStore` 的实时批次、
- `scripts/logging/`
- `shared/logging/`
- `macos/Sources/Lithe/Views/Diagnostics/`
- `macos/Sources/Lithe/Views/Language/LanguageServerLogsView.swift`
- `macos/Sources/Lithe/Application/Features/LanguageServerLogsModel.swift`
- `macos/Sources/Lithe/Services/Diagnostics/`
- `macos/Sources/Lithe/Platform/MacOS/Logging/`
- `macos/Sources/Lithe/Platform/MacOS/Diagnostics/`
Expand Down
Loading
Loading