package logx import ( "fmt" "io" "os" "path/filepath" "sync" "gopkg.in/natefinch/lumberjack.v2" "git.yuntuo.hk/k2safe/go-omni/internal/configdiag" ) // RotationConfig opts a file logger into size-based rotation. Zero limits use // 100 MiB, 10 backups and 7 days. Each file must have a single process owner. type RotationConfig struct { Enabled bool `json:"enabled"` MaxSizeMB int `json:"maxSizeMb"` MaxBackups int `json:"maxBackups"` MaxAgeDays int `json:"maxAgeDays"` Compress bool `json:"compress"` } func (r RotationConfig) validate(cfg Config) error { for _, f := range []struct { name string value, max int }{{"maxSizeMb", r.MaxSizeMB, 1024 * 1024}, {"maxBackups", r.MaxBackups, 10000}, {"maxAgeDays", r.MaxAgeDays, 36500}} { if f.value < 0 || f.value > f.max { return configdiag.New("rotation."+f.name, fmt.Sprintf("must be between 0 and %d", f.max)) } } if r.Enabled && cfg.File == "" { return configdiag.New("file", "is required when rotation is enabled") } if r.Enabled && cfg.Writer != nil { return configdiag.New("rotation.enabled", "cannot be enabled with a custom writer") } return nil } func rotatingWriter(cfg Config) (io.Writer, func() error, error) { r := cfg.Rotation if r.MaxSizeMB == 0 { r.MaxSizeMB = 100 } if r.MaxBackups == 0 { r.MaxBackups = 10 } if r.MaxAgeDays == 0 { r.MaxAgeDays = 7 } if err := os.MkdirAll(filepath.Dir(cfg.File), 0700); err != nil { return nil, nil, err } // Fail at setup, rather than silently dropping the first log record. f, err := os.OpenFile(cfg.File, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return nil, nil, err } if err = f.Close(); err != nil { return nil, nil, err } w := &rotationWriter{writer: &lumberjack.Logger{Filename: cfg.File, MaxSize: r.MaxSizeMB, MaxBackups: r.MaxBackups, MaxAge: r.MaxAgeDays, Compress: r.Compress}} return w, w.Close, nil } // Lumberjack can reopen after Close; prevent children of a closed logger from // resurrecting its file. The lock also serializes shutdown with pending writes. type rotationWriter struct { mu sync.Mutex writer *lumberjack.Logger closed bool } func (w *rotationWriter) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if w.closed { return 0, io.ErrClosedPipe } return w.writer.Write(p) } func (w *rotationWriter) Close() error { w.mu.Lock() defer w.mu.Unlock() if w.closed { return nil } w.closed = true return w.writer.Close() }