restructured logger from budigt
This commit is contained in:
parent
358ee6acc0
commit
b2fd12b1c8
11 changed files with 1227 additions and 1 deletions
113
file_rotation.go
Normal file
113
file_rotation.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package splinter
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const rotationTimestampFormat = "20060102T150405.000000000Z"
|
||||
|
||||
// shouldRotate reports whether the current file has hit a rotation trigger.
|
||||
// Caller must hold s.mu.
|
||||
func (s *FileStream) shouldRotate() bool {
|
||||
if s.cfg.MaxSizeMB > 0 && s.counter.n >= int64(s.cfg.MaxSizeMB)*1024*1024 {
|
||||
return true
|
||||
}
|
||||
if s.cfg.MaxAge > 0 && time.Since(s.openedAt) >= s.cfg.MaxAge {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// rotate closes the current file, renames it to a timestamped backup, opens
|
||||
// a fresh file, and kicks off async compression + pruning. Caller holds s.mu.
|
||||
func (s *FileStream) rotate() error {
|
||||
if err := s.file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ts := time.Now().UTC().Format(rotationTimestampFormat)
|
||||
ext := filepath.Ext(s.path)
|
||||
base := s.path[:len(s.path)-len(ext)]
|
||||
backupPath := fmt.Sprintf("%s.%s%s", base, ts, ext)
|
||||
|
||||
if err := os.Rename(s.path, backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(s.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.attach(f, 0)
|
||||
s.openedAt = time.Now()
|
||||
|
||||
go s.compressAndPrune(backupPath, base, ext)
|
||||
return nil
|
||||
}
|
||||
|
||||
// compressAndPrune runs in the background after rotation: gzips the new
|
||||
// backup (if configured) and deletes any stale backups beyond MaxBackups.
|
||||
// Errors are swallowed since this is best-effort housekeeping.
|
||||
func (s *FileStream) compressAndPrune(backupPath, base, ext string) {
|
||||
if s.cfg.Compress {
|
||||
_ = gzipFile(backupPath)
|
||||
}
|
||||
s.pruneBackups(base, ext)
|
||||
}
|
||||
|
||||
// gzipFile compresses src to src+".gz", then removes src on success.
|
||||
func gzipFile(src string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.OpenFile(src+".gz", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gz := gzip.NewWriter(out)
|
||||
|
||||
if _, err := io.Copy(gz, in); err != nil {
|
||||
gz.Close()
|
||||
out.Close()
|
||||
os.Remove(src + ".gz")
|
||||
return err
|
||||
}
|
||||
if err := gz.Close(); err != nil {
|
||||
out.Close()
|
||||
os.Remove(src + ".gz")
|
||||
return err
|
||||
}
|
||||
if err := out.Close(); err != nil {
|
||||
os.Remove(src + ".gz")
|
||||
return err
|
||||
}
|
||||
return os.Remove(src)
|
||||
}
|
||||
|
||||
// pruneBackups removes the oldest rotated files when the total exceeds
|
||||
// MaxBackups. Both raw and gzipped backups are considered.
|
||||
func (s *FileStream) pruneBackups(base, ext string) {
|
||||
raw, _ := filepath.Glob(fmt.Sprintf("%s.*%s", base, ext))
|
||||
gz, _ := filepath.Glob(fmt.Sprintf("%s.*%s.gz", base, ext))
|
||||
|
||||
all := append(raw, gz...)
|
||||
if len(all) <= s.cfg.MaxBackups {
|
||||
return
|
||||
}
|
||||
// Lex sort puts older timestamps first; trailing ".gz" sorts after the
|
||||
// raw form, so any transient duplicate during compression is handled
|
||||
// naturally on the next rotation.
|
||||
sort.Strings(all)
|
||||
for _, p := range all[:len(all)-s.cfg.MaxBackups] {
|
||||
_ = os.Remove(p)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue