Files
go-omni/logx/rotation.go

84 lines
2.1 KiB
Go

package logx
import (
"errors"
"io"
"os"
"path/filepath"
"sync"
"gopkg.in/natefinch/lumberjack.v2"
)
// 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 {
if r.MaxSizeMB < 0 || r.MaxSizeMB > 1024*1024 || r.MaxBackups < 0 || r.MaxBackups > 10000 || r.MaxAgeDays < 0 || r.MaxAgeDays > 36500 {
return errors.New("logx: invalid rotation limits")
}
if r.Enabled && (cfg.File == "" || cfg.Writer != nil) {
return errors.New("logx: rotation requires a file and no 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()
}