fix(m188): r1 复审 P2-1——后端启动失败的具名原因进 launcher.log

复审结论 p2-1/1 item + fix-then-merge:主线成立,但「启动器自己因 GPU / 渲染后端起不来」的
事故原因仍只进 stderr —— 双击启动没有 stderr,那条路径上等于没有日志。

- renderer/mod.rs::report_failure 的两行(`[gpu] <backend> 初始化失败(具名)` /
  `[gpu] 失败详情(具名)`)改走 diag!;
- renderer/log_bridge.rs 转出的 wgpu 日志(含 `…Selected: AdapterInfo…`,GPU 真生效的唯一证据
  行)改走 diag!;
- renderer/preflight.rs 的 `[gpu-preflight]`(ULL_PREFLIGHT=1)改走 diag!;
- probe.rs::apply_with_preflight 的三行 `[probe]` 改走 diag!。
- app/diag.rs 模块文档 + docs/logs-location.md §3 的判据改准:
  「逐帧度量不接;显式取证开关与一次性事故行可接」,并修掉 `[corner]` 两处自相矛盾
  (唯一打印点在 app/update/appearance.rs,走 diag! ⇒ 进日志;corner.rs 只 format_line)。
- 新守门 renderer::tests::a_backend_startup_failure_lands_in_the_launcher_log:
  断言那两行真的落进 launcher.log;变异(改回纯 eprintln)→ 红。
- README 两语的 launcher.log 行补上「渲染后端起不来的具名原因」。
This commit is contained in:
worker-logplace committed 2026-11-09 01:05:40 +08:00
1 parent 5fd5cd47dc
commit 8c5bebf3e3
9 files changed
+87 -27

No files matched your search

+3 -1
View File
@@ -88,7 +88,9 @@ impl log::Log for Bridge {
}
// iced_wgpu 用 `{:#?}` 打多行结构体;压成一行才好 grep / 贴验收报告。
let message = record.args().to_string().replace('\n', " ");
eprintln!("[gpu] {}: {}", record.target(), message.trim_end());
// M188 复审 P2-1:`[gpu] iced_wgpu: …Selected: AdapterInfo…` 是「GPU 真生效」的**唯一**
// 证据行,也是「为什么降级了」的第一现场,必须进 `<.ULL>/logs/launcher.log`。
crate::app::diag::diag!("[gpu] {}: {}", record.target(), message.trim_end());
}
fn flush(&self) {}
+45 -2
View File
@@ -199,9 +199,12 @@ pub fn forced_fallback() -> Option<Kind> {
/// `iced::Error::GraphicsCreationFailed` 的 `Display` **不包含**内部错误
/// (`#[error("the application graphics context could not be created")]`,没有 `{0}`),
/// 所以这里必须显式把内层错误打出来,否则又变回「只知道失败,不知道为什么」。
///
/// **M188 复审 P2-1**:这两行是**事故本体**,必须同时进 `<.ULL>/logs/launcher.log`——
/// 双击启动的 GUI 进程没有 stderr,只打 `eprintln!` 等于「最该有日志的那条路径上没有日志」。
pub fn report_failure(backend: Kind, error: &(impl std::fmt::Display + std::fmt::Debug)) {
eprintln!("[gpu] {} 初始化失败(具名):{error}", backend.name());
eprintln!("[gpu] 失败详情(具名):{error:?}");
crate::app::diag::diag!("[gpu] {} 初始化失败(具名):{error}", backend.name());
crate::app::diag::diag!("[gpu] 失败详情(具名):{error:?}");
}
#[cfg(test)]
@@ -246,4 +249,44 @@ mod tests {
});
assert_eq!(calls, 1, "进程内只许跑一次;重试只能换进程");
}
/// **M188 复审 P2-1 的守门**:后端起不来那两行必须进 `<.ULL>/logs/launcher.log`,不只是 stderr。
///
/// 为什么这条最要紧:双击启动的 GUI 进程**没有 stderr 去向**,而「GPU / 渲染后端起不来」
/// 正是最需要事后复盘的一类事故。只打 `eprintln!` 等于在这条路径上什么都没留下——
/// 用户那句「崩了但只有一个安装日志」会在最该有日志的地方继续成立。
///
/// 抓得住:`report_failure` 退回纯 `eprintln!`(变异证明:改回去本条立刻红)。
/// 抓不住:真机上 iced 起不来的样子(那要真 GPU/驱动,见报告「没验的部分」)。
#[test]
fn a_backend_startup_failure_lands_in_the_launcher_log() {
use crate::app::diag::LOG_STATE_LOCK;
use unlucky_core::launch::launcher_log;
// `launcher_log` 是进程级单例:与 `app/diag.rs` 的滚动闸、`app/dialog/tests.rs` 的启动事实
// 用例串行(同一个 `LOG_STATE_LOCK`)。
let _guard = LOG_STATE_LOCK
.lock()
.unwrap_or_else(|error| error.into_inner());
let dir = std::env::temp_dir().join(format!("ull-m188-gpu-fail-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("建夹具目录");
launcher_log::set_dir(&dir);
report_failure(Kind::Wgpu, &"no suitable adapter (host: test)".to_string());
let path = dir.join(launcher_log::FILE_NAME);
let text = std::fs::read_to_string(&path).expect("launcher.log");
assert!(
text.contains("[gpu] wgpu 初始化失败(具名):no suitable adapter"),
"具名原因没进日志:{text}"
);
assert!(
text.contains("[gpu] 失败详情(具名):"),
"失败详情没进日志:{text}"
);
// 取证:`cargo test … -- --nocapture` 时把真实落盘内容打出来(报告/复审可复核)。
println!("launcher.log:\n{text}");
std::fs::remove_dir_all(&dir).ok();
}
}
+6 -2
View File
@@ -76,10 +76,14 @@ pub fn run_if_requested() -> Option<Result<Verdict, Failure>> {
let started = std::time::Instant::now();
let verdict = run();
let took = started.elapsed().as_secs_f64() * 1000.0;
// M188:显式开关(`ULL_PREFLIGHT=1`)下的一次性行,按 `app::diag` 的判据可以进
// `launcher.log`——用户开这个开关就是为了拿到「为什么起不来」的证据。
match &verdict {
Ok(ok) => eprintln!("[gpu-preflight] {} took={took:.0}ms", ok.describe()),
Ok(ok) => {
crate::app::diag::diag!("[gpu-preflight] {} took={took:.0}ms", ok.describe());
}
Err(failure) => {
eprintln!("[gpu-preflight] {} took={took:.0}ms", failure.describe());
crate::app::diag::diag!("[gpu-preflight] {} took={took:.0}ms", failure.describe());
}
}
Some(verdict)