package logging import ( "os" "path/filepath" "sync" "time" ) // DailyWriter writes log entries to //. It keeps the // file handle open for the active day and rotates on the first write after // midnight, matching the directory layout consumed by the log viewer. type DailyWriter struct { mu sync.Mutex root string name string date string file *os.File retentionDay int } func NewDailyWriter(root, name string, retentionDay int) *DailyWriter { w := &DailyWriter{root: root, name: name, retentionDay: retentionDay} w.removeExpired() return w } func (w *DailyWriter) removeExpired() { if w.retentionDay <= 0 { return } entries, err := os.ReadDir(w.root) if err != nil { return } cutoff := time.Now().AddDate(0, 0, -w.retentionDay) for _, entry := range entries { if !entry.IsDir() { continue } date, err := time.Parse("2006-01-02", entry.Name()) if err == nil && date.Before(cutoff) { _ = os.RemoveAll(filepath.Join(w.root, entry.Name())) } } } func (w *DailyWriter) Write(value []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() date := time.Now().Format("2006-01-02") if w.file == nil || w.date != date { if w.file != nil { _ = w.file.Close() } directory := filepath.Join(w.root, date) if err := os.MkdirAll(directory, 0o755); err != nil { return 0, err } file, err := os.OpenFile(filepath.Join(directory, w.name), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return 0, err } w.file, w.date = file, date } return w.file.Write(value) } func (w *DailyWriter) Close() error { w.mu.Lock() defer w.mu.Unlock() if w.file == nil { return nil } err := w.file.Close() w.file = nil return err }