Skip to main content

futu_opend/
crash_log.rs

1//! v1.4.110 P1-2: crash log + panic hook helpers
2//!
3//! 抽自 main.rs lines 20-123 (v1.4.97 P1-D-D 实装).
4//! Aligned with C++ NNCrashCenter pattern.
5
6#![allow(unused_imports)]
7
8use anyhow::Result;
9
10/// v1.4.97 P1-D-D: directory holding dated crash logs.
11fn crash_log_dir() -> std::path::PathBuf {
12    // Reuse same `~/.futu-opend-rs/` root as credentials/keys.json (CLAUDE.md
13    // §登录 device_id 生命周期). Don't introduce new XDG / dirs crate dep.
14    let home = std::env::var_os("HOME")
15        .map(std::path::PathBuf::from)
16        .unwrap_or_else(|| std::path::PathBuf::from("/tmp"));
17    home.join(".futu-opend-rs").join("crashes")
18}
19
20/// v1.4.97 P1-D-D: synchronously write a crash log file.
21///
22/// Best-effort: if any step fails (mkdir, format, write), silently swallow.
23/// Panic in panic-hook is never recoverable.
24pub fn write_crash_log_file(info: &std::panic::PanicHookInfo<'_>) {
25    let dir = crash_log_dir();
26    let _ = std::fs::create_dir_all(&dir);
27
28    // dated filename `{YYYYMMDDhhmmss}.log` aligns with C++ NNCrashCenter
29    // (NNCrashCenter.cpp:6-54). Avoid chrono dep here — sync formatting via
30    // std::time::SystemTime + manual local-broken-down-time would require
31    // libc::localtime_r unsafe; prefer simple unix epoch + version + thread.
32    let now_secs = std::time::SystemTime::now()
33        .duration_since(std::time::UNIX_EPOCH)
34        .map(|d| d.as_secs())
35        .unwrap_or(0);
36    let path = dir.join(format!("crash-{now_secs}.log"));
37
38    let location = info
39        .location()
40        .map(|l| format!("{}:{}", l.file(), l.line()))
41        .unwrap_or_else(|| "<unknown>".to_string());
42    let payload = info
43        .payload()
44        .downcast_ref::<&str>()
45        .copied()
46        .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.as_str()))
47        .unwrap_or("<non-string panic payload>");
48    let thread = std::thread::current()
49        .name()
50        .unwrap_or("<unnamed>")
51        .to_string();
52    // v1.4.104 eli OBS-P3-001 fix: force capture (不依赖 RUST_BACKTRACE env).
53    // 之前 `Backtrace::capture()` 只在 RUST_BACKTRACE=1 时返实 backtrace, 否则
54    // "disabled backtrace" 占位 → debug 价值大减. force_capture() 总返实 stack.
55    // crash 路径已是 "已经挂了" panic, 多花几 ms 抓 stack 是值得的.
56    let backtrace = std::backtrace::Backtrace::force_capture();
57
58    let body = format!(
59        "v1.4.97 P1-D-D crash report\n\
60         daemon_version: {}\n\
61         unix_timestamp: {}\n\
62         os: {}\n\
63         arch: {}\n\
64         thread: {}\n\
65         location: {}\n\
66         payload: {}\n\
67         backtrace:\n{}\n",
68        env!("CARGO_PKG_VERSION"),
69        now_secs,
70        std::env::consts::OS,
71        std::env::consts::ARCH,
72        thread,
73        location,
74        payload,
75        backtrace,
76    );
77    let _ = std::fs::write(&path, body);
78}
79
80/// v1.4.97 P1-D-D: at startup, scan crash dir for previous crashes, emit a
81/// WARN log with the latest filename so ops sees "daemon previously crashed
82/// at <ts>" without manual file inspection. Best-effort, silent on errors.
83pub fn warn_if_previous_crash() {
84    let dir = crash_log_dir();
85    let Ok(entries) = std::fs::read_dir(&dir) else {
86        return;
87    };
88    // Find latest crash-*.log by mtime
89    let mut latest: Option<(std::time::SystemTime, std::path::PathBuf)> = None;
90    for entry in entries.flatten() {
91        let path = entry.path();
92        if !path
93            .file_name()
94            .and_then(|s| s.to_str())
95            .is_some_and(|s| s.starts_with("crash-") && s.ends_with(".log"))
96        {
97            continue;
98        }
99        let Ok(meta) = entry.metadata() else { continue };
100        let Ok(mtime) = meta.modified() else { continue };
101        if latest.as_ref().is_none_or(|(t, _)| mtime > *t) {
102            latest = Some((mtime, path));
103        }
104    }
105    if let Some((_, path)) = latest {
106        // Use eprintln since this runs before tracing init.
107        eprintln!(
108            "⚠️  v1.4.97 P1-D-D: previous crash log detected at {}",
109            path.display()
110        );
111        eprintln!("    inspect for last-known panic payload + backtrace.");
112    }
113}