51 lines
1.3 KiB
Go
51 lines
1.3 KiB
Go
// Package crash 崩溃兜底:panic 时把堆栈写入崩溃日志(闪退可诊断)。
|
||
package crash
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime/debug"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
var (
|
||
logMu sync.Mutex
|
||
logPath string
|
||
)
|
||
|
||
// SetLogPath 设置崩溃日志路径(main 启动时调用一次)。
|
||
func SetLogPath(p string) { logPath = p }
|
||
|
||
func writeLog(msg string) {
|
||
logMu.Lock()
|
||
defer logMu.Unlock()
|
||
msg = fmt.Sprintf("[%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), msg)
|
||
if logPath != "" {
|
||
_ = os.MkdirAll(filepath.Dir(logPath), 0o755)
|
||
if f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644); err == nil {
|
||
_, _ = f.WriteString(msg)
|
||
_ = f.Close()
|
||
}
|
||
}
|
||
_, _ = fmt.Fprint(os.Stderr, msg)
|
||
}
|
||
|
||
// Guard 在 main 顶部 defer Guard();panic 时写崩溃日志后重抛(保留退出行为)。
|
||
func Guard(logPath string) {
|
||
if r := recover(); r != nil {
|
||
writeLog(fmt.Sprintf("崩溃(主线程): %v\n%s", r, debug.Stack()))
|
||
panic(r) // 保留原始 panic 行为(退出码非 0)
|
||
}
|
||
}
|
||
|
||
// RecoverGo 非主 goroutine 的兜底:panic 写日志后终止进程
|
||
// (Go 中任何 goroutine 未恢复的 panic 都会导致整个进程静默退出)。
|
||
func RecoverGo(name string) {
|
||
if r := recover(); r != nil {
|
||
writeLog(fmt.Sprintf("崩溃(goroutine %s): %v\n%s", name, r, debug.Stack()))
|
||
os.Exit(2)
|
||
}
|
||
}
|