# 日志在哪(M188) 一句话:**启动器自己写的东西全在 `<数据根>/logs/` 一个目录里**,游戏自己的东西在实例运行目录里。 设置页那行「日志文件夹」显示的就是前者的绝对路径,旁边有 *打开文件夹* 与 *复制路径*;日志页页头 也有 *打开文件夹*。两个入口走同一个落点(`crates/app-iced/src/app/logs_folder.rs`),失败时输出的 文本带完整路径。 ## 1. 三个数据模式下的绝对路径 `<数据根>` 由 `core::platform::data_dir` 的三模式解析(便携 / 标准 / 自定义): | 模式 | 启动器数据目录(= `<数据根>`) | 日志根 | |---|---|---| | portable | `/.ULL` | `/.ULL/logs` | | standard | Windows `%APPDATA%\ULL` · macOS `~/Library/Application Support/ULL` · Linux `$XDG_DATA_HOME/ULL`(缺省 `~/.local/share/ULL`) | `<上者>/logs` | | custom | `<自定义根>/.ULL` | `<自定义根>/.ULL/logs` | GUI 的 `--data-dir ` 是 custom 模式的一种入口:那时日志根是 `/.ULL/logs`。设置页显示的 路径与 `launcher.log` 的实际落点取的是**同一份解析结果**(`SettingsBridge` → `/config/launcher.json` → ``),所以两者不会各说各话。 ## 2. 目录里有什么 | 路径 | 写者 | 是什么 | |---|---|---| | `logs/launcher.log` | `core::launch::launcher_log` | 启动器自己的运行日志:启动拍(主题 / 语言 / 路由 / 窗口)、后端探测与降级、字体链、每一次启动的命令行事实 / spawn 结果 / 退出码、打开日志目录等用户动作的结果。 | | `logs/launcher.1.log` | 同上(滚动) | 上一份日志。**只留一份**历史。 | | `logs/game/.jsonl` | `core::launch::session` | 每次启动一份 JSONL 事件流(命令行已脱敏、每行 stdout/stderr、退出、诊断)。 | | `logs/game/index.jsonl` | `core::launch::session_index` | 一会话一行摘要的索引,日志页左栏读它。 | | `logs/install.log` | `presenter::host::install_journal` | 安装流水,每阶段一行。 | | `logs/session-log-location.json` | `presenter::host::logs::note_location` | 会话日志现在在哪 / 以前在哪 / 搬没搬(`moved: 0`),每次启动重写。 | | `data/agent/sessions/game/…` | **旧版本** | 只读兼容,见 §4。 | ### 上限与滚动 - 单文件上限 `launcher_log::MAX_BYTES = 1 MiB`;每次追加前检查,超过就把 `launcher.log` 改名成 `launcher.1.log`(旧的那份被替换),新文件从 0 开始。 - 保留份数:**1**。所以 `logs/` 里启动器日志的稳态占用 ≤ 约 2 MiB(+ 同目录的会话 jsonl)。 - 为什么不做按日期滚动:事故复现往往在「刚刚那一次」,份数比历史长度更重要;而无限份数会让「导出 诊断包」变成搬砖。 ## 3. 哪些行进 launcher.log、哪些只进 stderr 判据只有一条:**事故之后还要不要看它**。实现是 `crates/app-iced/src/app/diag.rs` 的 `diag!` 宏 (stderr + 文件双写,行首 `[tag]` 就是日志里的 scope)。 | 进 launcher.log(`crates/app-iced/src/app/**` 里打的行) | 只进 stderr(本 mission scope 之外的行) | |---|---| | 启动编排:`[probe]`(降级重开 / 探测耗时)、`[font]`、`[theme]`、`[appearance]`、`[gpu]` 的降级与拒绝降级、`[boot]`(含后端 request/effective)、`[shot]` 的通道提示 | `renderer::acceptance` 的验收度量:`[window]` / `[perf]` / `[caps]` / `[theme] source=…` | | 用户动作的结果:`[logs]`(打开日志文件夹成功/失败)、`[settings]` 写盘失败、`[i18n]` 语言没落盘、`[update]` 的忽略/重启、`[agent]` / `[content]` / `[page]` 的落点提示 | `renderer` 自己的 `[gpu] …初始化失败`、`[gpu] iced_wgpu: Selected`、`[probe] request=…`(`probe.rs`)、`[corner]`(`platform/win32/corner.rs`) | | 一次性、量很小的诊断(每次启动几行到几十行) | 组件几何探针与测试:`[feedback-probe]` / `[gallery]` / `[probe] divider…` | 不允许把高频行接进来:1 MiB 的上限就是为了保住「事故现场」,逐帧写会把它冲掉。 **已知边界(M188 没做,已登记)**:`renderer/**`、`probe.rs`、`platform/win32/corner.rs` 里的行 仍然只走 stderr——它们各自属于别的 mission 的 scope。要全线接通,得把这些文件的 `eprintln!` 也换成 `crate::app::diag::diag!`(或把 `emit` 提成 lib 级函数)。 ## 4. 会话日志搬家:策略是**不搬** M188 把新会话从 `data/agent/sessions/game/` 挪到 `logs/game/`(目录名里的 `agent` 是历史遗留, 它从来只装游戏会话)。旧目录的处理方式: - **原样保留、继续读**:`presenter::host::logs` 与 `cli` 的会话读取都是两个根合并,同一个 `session_id` 同时存在时以 `logs/` 那条为准; - **不搬、不删**:搬运用户已有数据是不可逆动作,中途失败会留下半份会话,崩溃弹窗里的会话关联也会断; - **留记录**:`logs/session-log-location.json` 写明两个根、旧根在不在、旧根里几个文件、`moved: 0`、 `policy: "read-only-compat"`。 ## 5. 游戏自己的日志(不归启动器写) 在实例的运行目录里(隔离实例是 `<游戏目录>/versions/<隔离名>/…`,共享实例直接是 `.minecraft`): - `logs/latest.log`、`logs/<日期>-<序号>.log.gz` —— 游戏自己的日志; - `crash-reports/crash-*.txt` —— **只在 JVM 活到崩溃处理器那一步时才有**; - `hs_err_pid*.log` —— JVM 自己崩(native crash)时才写。 模组 / 缺库 / Java 选错把游戏弄死在 Minecraft 初始化之前时,上面三个都不会出现——那不是启动器 丢了日志,而是游戏没机会写。那种时候的证据是 `logs/game/.jsonl` 与 `logs/launcher.log`。 ## 6. 没验的部分 - **真机「双击启动后日志确实在」**:本轮只在开发机上用 `cargo test` 与本地进程验证,Windows 桌面 双击启动需要用户实测。 - **macOS / Linux 的日志根**:路径由同一条解析链给出(`standard_launcher_home` 有单测覆盖三个平台), 但「Mac 上点打开文件夹真的会开 Finder」「Linux 上没有 `xdg-open` 时会怎样」没有真机验证。 - **只读数据目录**:日志写不进去时按设计静默放过(best-effort),此时设置页显示的路径仍然正确, 但文件不存在。