package utils import ( "fmt" "os" "path/filepath" "sync" "time" "github.com/wailsapp/wails/v2/pkg/logger" ) var ( baseTargets []logger.Logger once sync.Once initErr error ) // InstancedLogger prefixes each message with its instance-specific prefix and a timestamp, // while writing to shared log outputs. type InstancedLogger struct { prefix string targets []logger.Logger } // NewLogger returns a logger instance that writes to the shared logs // // but prefixes every message with [prefix]. func NewLogger(prefix string) (*InstancedLogger, error) { once.Do(func() { baseTargets, initErr = createBaseTargets() }) if initErr != nil { return nil, initErr } return &InstancedLogger{prefix: prefix, targets: baseTargets}, nil } // createBaseTargets initializes the shared log file and console loggers once. func createBaseTargets() ([]logger.Logger, error) { 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 logs older than 14 days 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())) } } // Remove old runtime_latest.log if it exists runtimePath := filepath.Join(logDir, "runtime_latest.log") if _, err := os.Stat(runtimePath); err == nil { _ = os.Remove(runtimePath) } // Prepare file paths 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 tsLogger := logger.NewFileLogger(tsPath) latestLogger := logger.NewFileLogger(latestPath) termLogger := logger.NewDefaultLogger() return []logger.Logger{tsLogger, latestLogger, termLogger}, nil } // log dispatches a timestamped, prefixed message to all shared targets. func (l *InstancedLogger) log(level, message string) { timestamp := time.Now().Format("2006-01-02 15:04:05") fullMsg := fmt.Sprintf("%s [%s] %s", timestamp, l.prefix, message) for _, t := range l.targets { switch level { case "Print": t.Print(fullMsg) case "Trace": t.Trace(fullMsg) case "Debug": t.Debug(fullMsg) case "Info": t.Info(fullMsg) case "Warning": t.Warning(fullMsg) case "Error": t.Error(fullMsg) case "Fatal": t.Fatal(fullMsg) } } } func (l *InstancedLogger) Print(message string) { l.log("Print", message) } func (l *InstancedLogger) Trace(message string) { l.log("Trace", message) } func (l *InstancedLogger) Debug(message string) { l.log("Debug", message) } func (l *InstancedLogger) Info(message string) { l.log("Info", message) } func (l *InstancedLogger) Warning(message string) { l.log("Warning", message) } func (l *InstancedLogger) Error(message string) { l.log("Error", message) } func (l *InstancedLogger) Fatal(message string) { l.log("Fatal", message) }