package common import ( "fmt" "os" "path/filepath" "runtime/debug" "sync" "time" ) var panicLogMu sync.Mutex // AppendPanicLog 把一次 panic 的来源名、值与完整栈追加写入 // ~/.u-desk/logs/panic-YYYYMMDD.log。写失败静默放弃:取证失败不能引发二次故障。 func AppendPanicLog(name string, r any) { dir := GetLogsDir() panicLogMu.Lock() defer panicLogMu.Unlock() if err := os.MkdirAll(dir, 0755); err != nil { return } path := filepath.Join(dir, fmt.Sprintf("panic-%s.log", time.Now().Format("20060102"))) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return } defer f.Close() // debug.Stack 在 defer 内 panic 未展开时调用,包含 panic 原始调用栈 fmt.Fprintf(f, "[%s] goroutine=%s panic=%v\n%s\n", time.Now().Format("2006-01-02 15:04:05.000"), name, r, debug.Stack()) } // Go 带 panic 兜底的 goroutine:panic 时写完整取证后仅结束该 goroutine, // 不让单点 panic 杀掉整个进程(windowsgui 下默认静默退出零痕迹)。 func Go(name string, fn func()) { go func() { defer func() { if r := recover(); r != nil { AppendPanicLog(name, r) } }() fn() }() }