package sink import ( "fmt" "os" "path/filepath" "sync" "logging-system/logsys" ) // File 以互斥锁保护的方式追加日志,可选按大小滚动。 // MaxBytes 为 0 时不滚动。Keep 是保留的历史文件份数(不含当前文件)。 type File struct { mu sync.Mutex Path string Format string MaxBytes int64 Keep int f *os.File size int64 } // Write 追加一条日志,必要时先滚动。 func (f *File) Write(e logsys.Entry) error { line, err := formatLine(f.Format, e, false) if err != nil { return err } f.mu.Lock() defer f.mu.Unlock() if err := f.open(); err != nil { return err } if f.MaxBytes > 0 && f.size > 0 && f.size+int64(len(line)) > f.MaxBytes { if err := f.rotate(); err != nil { return err } } n, err := f.f.Write(line) f.size += int64(n) return err } // Sync 把文件缓冲刷到磁盘。 func (f *File) Sync() error { f.mu.Lock() defer f.mu.Unlock() if f.f == nil { return nil } return f.f.Sync() } // Close 关闭文件句柄。 func (f *File) Close() error { f.mu.Lock() defer f.mu.Unlock() if f.f == nil { return nil } err := f.f.Close() f.f = nil return err } func (f *File) open() error { if f.f != nil { return nil } if f.Path == "" { return fmt.Errorf("sink: file path is required") } if err := os.MkdirAll(filepath.Dir(f.Path), 0o755); err != nil { return err } fp, err := os.OpenFile(f.Path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return err } info, err := fp.Stat() if err != nil { _ = fp.Close() return err } f.f = fp f.size = info.Size() return nil } func (f *File) rotate() error { if f.f != nil { if err := f.f.Close(); err != nil { return err } f.f = nil } keep := f.Keep if keep < 1 { keep = 1 } _ = os.Remove(fmt.Sprintf("%s.%d", f.Path, keep)) for i := keep - 1; i >= 1; i-- { src := fmt.Sprintf("%s.%d", f.Path, i) dst := fmt.Sprintf("%s.%d", f.Path, i+1) if _, err := os.Stat(src); err == nil { _ = os.Rename(src, dst) } } if err := os.Rename(f.Path, f.Path+".1"); err != nil && !os.IsNotExist(err) { return err } f.size = 0 return f.open() }