Files
Unlucky-Launcher/crates/app-iced/src/renderer/log_bridge.rs
T
worker-logplace 8c5bebf3e3 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 行补上「渲染后端起不来的具名原因」。
2026-11-09 01:05:40 +08:00

154 lines
5.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 把 `iced_wgpu` / `wgpu` 的 `log` 输出转到 stderr(判据表第 8 行的取证通道)。
//!
//! ## 为什么需要它
//!
//! `iced_wgpu` 在**真的**创建设备时会自己打一行:
//!
//! ```text
//! Selected: AdapterInfo { name: "Apple M1", backend: Metal, device_type: IntegratedGpu, … }
//! ```
//!
//! 这行只有「wgpu 走到适配器那一步」才会出现,所以它是**GPU 真生效的实证**——不是我们的推断。
//! 但它走 `log` crate,而本进程从来没装过 logger,于是 M1 那份报告里这行根本不存在:
//! 「看不到」被当成了「没有」。装一个只放行 GPU 相关 target 的 logger,成本 60 行,
//! 换来的是以后每次验收都能直接看见 wgpu 说了什么。
//!
//! ## 过滤
//!
//! 按 **target 分级**,不是一刀切:`wgpu_hal` / `wgpu_core` 在 Info 级别会刷出上百行 Vulkan
//! loader 噪声(M97 实测:启动时 90 行 registry 扫描),把真正有用的
//! `iced_wgpu: Selected: AdapterInfo{…}` 埋掉。
//!
//! | `ULL_GPU_LOG` | `iced_wgpu*` | `wgpu*`(hal / core) | 用途 |
//! |---|---|---|---|
//! | 未设 / `info` | Info | **Warn** | 默认:看得见选中的适配器/格式 + wgpu 的告警 |
//! | `warn` | Warn | Warn | 只留告警(启动日志最干净) |
//! | `debug` | Debug | Debug | 全量(诊断用;含 Vulkan loader 噪声) |
//! | `off` | — | — | 不装 logger(回到 M97 之前,仅用于对照实验) |
//!
//! 其它 target(`winit`、`iced_winit` …)一律不放行:它们只会把启动日志淹掉。
use std::sync::Once;
/// 装 logger(进程内只生效一次;重复调用无害)。
pub fn install() {
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let Some(level) = level_from_env() else {
return;
};
// `set_boxed_logger` 只能成功一次;已被别人装过就安静让位,不 panic。
if log::set_boxed_logger(Box::new(Bridge { level })).is_ok() {
log::set_max_level(level);
}
});
}
/// `ULL_GPU_LOG` → 过滤级别;`None` = 别装(`off`)。
fn level_from_env() -> Option<log::LevelFilter> {
parse_level(&std::env::var("ULL_GPU_LOG").unwrap_or_default())
}
/// 取值解释(纯函数,测试直接喂字符串,不碰进程环境)。
fn parse_level(value: &str) -> Option<log::LevelFilter> {
match value.trim().to_ascii_lowercase().as_str() {
"off" | "0" | "no" | "false" => None,
"warn" | "warning" => Some(log::LevelFilter::Warn),
"debug" => Some(log::LevelFilter::Debug),
"trace" => Some(log::LevelFilter::Trace),
_ => Some(log::LevelFilter::Info),
}
}
/// 某个 target 在这个总级别下实际放行到哪一级。
///
/// `wgpu_hal`/`wgpu_core` 在 `info` 档被压到 `warn`:它们的 info 是 Vulkan loader 的原文,
/// 对「到底用了哪个后端」没有信息量,只有噪声。
fn target_level(level: log::LevelFilter, target: &str) -> log::LevelFilter {
if level == log::LevelFilter::Info && !target.starts_with("iced_wgpu") {
return log::LevelFilter::Warn;
}
level
}
/// 只放行渲染后端相关 target 的 logger。
struct Bridge {
level: log::LevelFilter,
}
impl log::Log for Bridge {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
let target = metadata.target();
is_gpu_target(target) && metadata.level() <= target_level(self.level, target)
}
fn log(&self, record: &log::Record<'_>) {
if !self.enabled(record.metadata()) {
return;
}
// iced_wgpu 用 `{:#?}` 打多行结构体;压成一行才好 grep / 贴验收报告。
let message = record.args().to_string().replace('\n', " ");
// 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) {}
}
/// `iced_wgpu::window::compositor` / `wgpu_core::…` / `wgpu_hal::…` 都算。
fn is_gpu_target(target: &str) -> bool {
["iced_wgpu", "wgpu"]
.iter()
.any(|prefix| target.starts_with(prefix))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_gpu_targets_pass() {
assert!(is_gpu_target("iced_wgpu::window::compositor"));
assert!(is_gpu_target("wgpu_core::instance"));
assert!(is_gpu_target("wgpu_hal::metal"));
assert!(!is_gpu_target("iced_winit::lib"));
assert!(!is_gpu_target("winit::event_loop"));
}
#[test]
fn level_env_values_are_interpreted() {
assert_eq!(parse_level(""), Some(log::LevelFilter::Info));
assert_eq!(parse_level("info"), Some(log::LevelFilter::Info));
assert_eq!(parse_level(" WARN "), Some(log::LevelFilter::Warn));
assert_eq!(parse_level("off"), None);
assert_eq!(parse_level("0"), None);
}
/// 默认档(`info`)下 `wgpu_hal`/`wgpu_core` 被压到 Warn:它们是 Vulkan loader 原文,
/// M97 实测一屏 90 行噪声,会把 `Selected: AdapterInfo` 埋掉。
#[test]
fn default_level_mutes_wgpu_noise_but_keeps_adapter_line() {
use log::LevelFilter;
assert_eq!(
target_level(LevelFilter::Info, "iced_wgpu::window::compositor"),
LevelFilter::Info,
"选中适配器那行必须留"
);
assert_eq!(
target_level(LevelFilter::Info, "wgpu_hal::vulkan::instance"),
LevelFilter::Warn,
"Vulkan loader 噪声必须压掉"
);
assert_eq!(
target_level(LevelFilter::Info, "wgpu_core::instance"),
LevelFilter::Warn
);
// 显式要 debug 时全放行(诊断用)。
assert_eq!(
target_level(LevelFilter::Debug, "wgpu_hal::vulkan::instance"),
LevelFilter::Debug
);
}
}