diff --git a/internal/logging/logging.go b/internal/logging/logging.go index db71dcc..aade5e4 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "runtime/debug" "time" ) @@ -80,3 +81,50 @@ func FilePath() string { func History() []string { return history } + +// Error logs an error message with a category (always logged, even when debug is off) +func Error(cat Category, format string, args ...interface{}) { + msg := fmt.Sprintf("%s ERROR: %s", cat, fmt.Sprintf(format, args...)) + timestamp := time.Now().Format(time.RFC3339Nano) + if file != nil { + fmt.Fprintf(file, "%s %s\n", timestamp, msg) + } + history = append(history, fmt.Sprintf("%s %s", timestamp, msg)) + if len(history) > historyMax { + history = history[len(history)-historyMax:] + } + logger.Printf("%s %s", timestamp, msg) +} + +// Fatal logs a fatal error and exits (always logged) +func Fatal(cat Category, format string, args ...interface{}) { + msg := fmt.Sprintf("%s FATAL: %s", cat, fmt.Sprintf(format, args...)) + timestamp := time.Now().Format(time.RFC3339Nano) + if file != nil { + fmt.Fprintf(file, "%s %s\n", timestamp, msg) + file.Sync() + } + logger.Printf("%s %s", timestamp, msg) + os.Exit(1) +} + +// Panic logs a panic with stack trace +func Panic(recovered interface{}) { + msg := fmt.Sprintf("%s PANIC: %v\nStack trace:\n%s", CatSystem, recovered, string(debug.Stack())) + timestamp := time.Now().Format(time.RFC3339Nano) + if file != nil { + fmt.Fprintf(file, "%s %s\n", timestamp, msg) + file.Sync() + } + history = append(history, fmt.Sprintf("%s %s", timestamp, msg)) + logger.Printf("%s %s", timestamp, msg) +} + +// RecoverPanic should be used with defer to catch and log panics +func RecoverPanic() { + if r := recover(); r != nil { + Panic(r) + // Re-panic to let the program crash with the logged info + panic(r) + } +} diff --git a/internal/queue/queue.go b/internal/queue/queue.go index dd852a3..32825d9 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -8,6 +8,8 @@ import ( "path/filepath" "sync" "time" + + "git.leaktechnologies.dev/stu/VideoTools/internal/logging" ) // JobType represents the type of job to execute @@ -373,6 +375,7 @@ func (q *Queue) ResumeAll() { // processJobs continuously processes pending jobs func (q *Queue) processJobs() { + defer logging.RecoverPanic() // Catch and log any panics in job processing for { q.mu.Lock() if !q.running { diff --git a/main.go b/main.go index 8dd3f14..4d104de 100644 --- a/main.go +++ b/main.go @@ -5945,6 +5945,7 @@ func (s *appState) stopPlayer() { func main() { logging.Init() defer logging.Close() + defer logging.RecoverPanic() // Catch and log any panics with stack trace flag.Parse() logging.SetDebug(*debugFlag || os.Getenv("VIDEOTOOLS_DEBUG") != "")