139 lines
3 KiB
Go
139 lines
3 KiB
Go
package admin
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type rotatingLogWriter struct {
|
|
path string
|
|
maxBytes int64
|
|
maxBackups int
|
|
file *os.File
|
|
size int64
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func ConfigureLogging(cfg Config) (io.Closer, error) {
|
|
if cfg.LogFile == "" {
|
|
return nil, nil
|
|
}
|
|
writer, err := newRotatingLogWriter(cfg.LogFile, cfg.LogMaxBytes, cfg.LogMaxBackups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
multi := io.MultiWriter(os.Stdout, writer)
|
|
log.SetOutput(multi)
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.LUTC)
|
|
return writer, nil
|
|
}
|
|
|
|
func newRotatingLogWriter(path string, maxBytes int64, maxBackups int) (*rotatingLogWriter, error) {
|
|
if maxBytes <= 0 {
|
|
maxBytes = 10 * 1024 * 1024
|
|
}
|
|
if maxBackups <= 0 {
|
|
maxBackups = 5
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return nil, fmt.Errorf("create log dir: %w", err)
|
|
}
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open log file: %w", err)
|
|
}
|
|
info, err := file.Stat()
|
|
if err != nil {
|
|
file.Close()
|
|
return nil, fmt.Errorf("stat log file: %w", err)
|
|
}
|
|
return &rotatingLogWriter{
|
|
path: path,
|
|
maxBytes: maxBytes,
|
|
maxBackups: maxBackups,
|
|
file: file,
|
|
size: info.Size(),
|
|
}, nil
|
|
}
|
|
|
|
func (w *rotatingLogWriter) Write(p []byte) (int, error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
if w.size+int64(len(p)) > w.maxBytes {
|
|
if err := w.rotateLocked(); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
n, err := w.file.Write(p)
|
|
w.size += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (w *rotatingLogWriter) Close() error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
if w.file == nil {
|
|
return nil
|
|
}
|
|
err := w.file.Close()
|
|
w.file = nil
|
|
return err
|
|
}
|
|
|
|
func (w *rotatingLogWriter) rotateLocked() error {
|
|
if w.file != nil {
|
|
if err := w.file.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
stamp := time.Now().UTC().Format("20060102T150405")
|
|
rotated := fmt.Sprintf("%s.%s", w.path, stamp)
|
|
if err := os.Rename(w.path, rotated); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("rotate log: %w", err)
|
|
}
|
|
if err := w.pruneLocked(); err != nil {
|
|
return err
|
|
}
|
|
file, err := os.OpenFile(w.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
return fmt.Errorf("open rotated log file: %w", err)
|
|
}
|
|
w.file = file
|
|
w.size = 0
|
|
return nil
|
|
}
|
|
|
|
func (w *rotatingLogWriter) pruneLocked() error {
|
|
pattern := w.path + ".*"
|
|
matches, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
return fmt.Errorf("list rotated logs: %w", err)
|
|
}
|
|
if len(matches) <= w.maxBackups {
|
|
return nil
|
|
}
|
|
sortStrings(matches)
|
|
for _, path := range matches[:len(matches)-w.maxBackups] {
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("remove old log %s: %w", path, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sortStrings(values []string) {
|
|
for i := 1; i < len(values); i++ {
|
|
value := values[i]
|
|
j := i - 1
|
|
for ; j >= 0 && values[j] > value; j-- {
|
|
values[j+1] = values[j]
|
|
}
|
|
values[j+1] = value
|
|
}
|
|
}
|