log.go

 1package log
 2
 3import (
 4	"fmt"
 5	"io"
 6	"log/slog"
 7	"os"
 8	"runtime/debug"
 9	"sync"
10	"sync/atomic"
11	"time"
12
13	"github.com/charmbracelet/crush/internal/event"
14	"github.com/charmbracelet/x/term"
15	"gopkg.in/natefinch/lumberjack.v2"
16)
17
18var (
19	initOnce    sync.Once
20	initialized atomic.Bool
21)
22
23func Setup(logFile string, debug bool, ws ...io.Writer) {
24	initOnce.Do(func() {
25		logRotator := &lumberjack.Logger{
26			Filename:   logFile,
27			MaxSize:    10,    // Max size in MB
28			MaxBackups: 0,     // Number of backups
29			MaxAge:     30,    // Days
30			Compress:   false, // Enable compression
31		}
32
33		level := slog.LevelInfo
34		if debug {
35			level = slog.LevelDebug
36		}
37
38		opts := &slog.HandlerOptions{
39			Level:     level,
40			AddSource: true,
41		}
42
43		var handlers []slog.Handler
44		handlers = append(handlers, slog.NewJSONHandler(logRotator, opts))
45
46		for _, w := range ws {
47			if w == nil {
48				continue
49			}
50			if f, ok := w.(term.File); ok && term.IsTerminal(f.Fd()) {
51				handlers = append(handlers, slog.NewTextHandler(w, opts))
52			} else {
53				handlers = append(handlers, slog.NewJSONHandler(w, opts))
54			}
55		}
56
57		slog.SetDefault(slog.New(slog.NewMultiHandler(handlers...)))
58		initialized.Store(true)
59	})
60}
61
62func Initialized() bool {
63	return initialized.Load()
64}
65
66func RecoverPanic(name string, cleanup func()) {
67	if r := recover(); r != nil {
68		event.Error(r, "panic", true, "name", name)
69
70		// Create a timestamped panic log file
71		timestamp := time.Now().Format("20060102-150405")
72		filename := fmt.Sprintf("crush-panic-%s-%s.log", name, timestamp)
73
74		file, err := os.Create(filename)
75		if err == nil {
76			defer file.Close()
77
78			// Write panic information and stack trace
79			fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r)
80			fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339))
81			fmt.Fprintf(file, "Stack Trace:\n%s\n", debug.Stack())
82
83			// Execute cleanup function if provided
84			if cleanup != nil {
85				cleanup()
86			}
87		}
88	}
89}