package crashlog import ( "fmt" "os" "path/filepath" "runtime/debug" "sync" "time" "tea.chunkbyte.com/kato/go-worm/lib/config" ) var mu sync.Mutex // Recover logs a panic without re-raising (used by optional defer wrappers). func Recover() { if r := recover(); r != nil { _ = write("panic", fmt.Sprint(r), debug.Stack()) } } // RecordPanic writes a recovered panic without exiting (e.g. HTTP handlers). func RecordPanic(recovered any) { _ = write("panic", fmt.Sprint(recovered), debug.Stack()) } // RecordError writes a fatal error before exit. func RecordError(err error) { if err == nil { return } _ = write("fatal", err.Error(), debug.Stack()) } func write(kind, detail string, stack []byte) error { dir, err := config.LogsDir() if err != nil { return err } mu.Lock() defer mu.Unlock() if err := os.MkdirAll(dir, 0o700); err != nil { return err } name := fmt.Sprintf("crash-%s.log", time.Now().Format("2006-01-02-150405")) path := filepath.Join(dir, name) body := fmt.Sprintf( "time: %s\nversion: %s\nkind: %s\ndetail: %s\n\nstack:\n%s", time.Now().Format(time.RFC3339Nano), config.Version, kind, detail, stack, ) return os.WriteFile(path, []byte(body), 0o600) }