feat(logging): Add panic recovery and error logging for UI crashes

- Added Error() and Fatal() logging functions for non-debug errors
- Added Panic() function to log panics with full stack traces
- Added RecoverPanic() for defer statements to catch crashes
- Added panic recovery to main() function
- Added panic recovery to queue job processing goroutine
- All panics now logged to videotools.log with timestamps and stack traces
- Helps diagnose UI crashes that occur during FFmpeg processing
This commit is contained in:
Stu Leak 2025-12-31 18:00:34 -05:00
parent 9c63ce03a3
commit 21119e1d5a
3 changed files with 52 additions and 0 deletions

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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") != "")