95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
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()
|
|
}
|