package utils import ( "fmt" "os" "path/filepath" "time" "github.com/wailsapp/wails/v2/pkg/logger" ) // multiLogger fans every log call out to multiple logger.Logger targets with timestamps. type multiLogger struct { targets []logger.Logger } // timestamped prefixes each message with a timestamp. func timestamped(message string) string { return time.Now().Format("2006-01-02 15:04:05") + " " + message } func (m *multiLogger) Print(message string) { msg := timestamped(message) for _, l := range m.targets { l.Print(msg) } } func (m *multiLogger) Trace(message string) { msg := timestamped(message) for _, l := range m.targets { l.Trace(msg) } } func (m *multiLogger) Debug(message string) { msg := timestamped(message) for _, l := range m.targets { l.Debug(msg) } } func (m *multiLogger) Info(message string) { msg := timestamped(message) for _, l := range m.targets { l.Info(msg) } } func (m *multiLogger) Warning(message string) { msg := timestamped(message) for _, l := range m.targets { l.Warning(msg) } } func (m *multiLogger) Error(message string) { msg := timestamped(message) for _, l := range m.targets { l.Error(msg) } } func (m *multiLogger) Fatal(message string) { msg := timestamped(message) for _, l := range m.targets { l.Fatal(msg) } } // NewRuntimeFileLogger returns a logger that writes all output both to // // $XDG_CONFIG_HOME/FanslySync/logs/runtime_latest.log (or OS equivalent) // // and to a timestamped file // // $XDG_CONFIG_HOME/FanslySync/logs/runtime_YYYY-MM-DD_HH-MM-SS.log (or OS equivalent) // // It also deletes any timestamped logs older than 14 days. // // The returned logger implements github.com/wailsapp/wails/v2/pkg/logger.Logger // and will be used by Wails for all Go-side logging. func NewRuntimeFileLogger() (logger.Logger, error) { // Make sure the log directory exists // We use $XDG_CONFIG_HOME/FanslySync/logs/runtime_latest.log or OS equivalent for $XDG_CONFIG_HOME cfgDir, err := os.UserConfigDir() if err != nil { return nil, fmt.Errorf("cannot determine user config dir: %w", err) } logDir := filepath.Join(cfgDir, "FanslySync", "logs") if err := os.MkdirAll(logDir, 0o755); err != nil { return nil, fmt.Errorf("cannot create log directory: %w", err) } // Prune old logs // We keep logs for 14 days, so delete any logs older than that cutoff := time.Now().Add(-14 * 24 * time.Hour) entries, _ := os.ReadDir(logDir) for _, e := range entries { if e.IsDir() || e.Name() == "runtime_latest.log" { continue } info, err := e.Info() if err != nil { continue } if info.ModTime().Before(cutoff) { _ = os.Remove(filepath.Join(logDir, e.Name())) } } ts := time.Now().Format("2006-01-02_15-04-05") tsPath := filepath.Join(logDir, fmt.Sprintf("runtime_%s.log", ts)) latestPath := filepath.Join(logDir, "runtime_latest.log") // Create loggers to attach to the multiLogger tsLogger := logger.NewFileLogger(tsPath) latestLogger := logger.NewFileLogger(latestPath) termLogger := logger.NewDefaultLogger() // Spread into a multiLogger // This will fan out all log messages to all three loggers multi := &multiLogger{ targets: []logger.Logger{tsLogger, latestLogger, termLogger}, } return multi, nil }