Files
hfb_sys/backend/internal/logging/daily_writer.go
T
yml2213 2efcede800 优化生产日志体积:访问降噪、压缩清理与退款告警节流
跳过轮询/封面等热路径成功请求,按日 gzip 并保留可配天数,退款 max-retry 汇总告警。
2026-07-13 22:02:20 +08:00

170 lines
3.7 KiB
Go

package logging
import (
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
type dailyWriter struct {
mu sync.Mutex
dir string
prefix string
location *time.Location
retainDays int
day string
file *os.File
}
func newDailyWriter(dir string, prefix string, location *time.Location, retainDays int) *dailyWriter {
if retainDays < 0 {
retainDays = 0
}
return &dailyWriter{
dir: dir,
prefix: prefix,
location: location,
retainDays: retainDays,
}
}
func (w *dailyWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if err := w.rotateIfNeeded(time.Now().In(w.location)); err != nil {
return 0, err
}
return w.file.Write(p)
}
func (w *dailyWriter) Sync() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.file == nil {
return nil
}
return w.file.Sync()
}
func (w *dailyWriter) rotateIfNeeded(now time.Time) error {
day := now.Format("2006-01-02")
if w.file != nil && w.day == day {
return nil
}
if err := os.MkdirAll(w.dir, 0o755); err != nil {
return err
}
prevDay := w.day
if w.file != nil {
_ = w.file.Close()
w.file = nil
}
path := filepath.Join(w.dir, fmt.Sprintf("%s-%s.log", w.prefix, day))
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return err
}
w.file = file
w.day = day
// 异步压缩昨日日志并清理过期文件,避免阻塞业务写路径
if prevDay != "" && prevDay != day {
go w.maintain(prevDay, now)
} else {
// 进程启动或首写:尝试压缩昨天 + 清理过期
yesterday := now.AddDate(0, 0, -1).Format("2006-01-02")
go w.maintain(yesterday, now)
}
return nil
}
func (w *dailyWriter) maintain(prevDay string, now time.Time) {
if prevDay != "" {
_ = w.compressDay(prevDay)
}
if w.retainDays > 0 {
_ = w.purgeOlderThan(now)
}
}
func (w *dailyWriter) compressDay(day string) error {
srcPath := filepath.Join(w.dir, fmt.Sprintf("%s-%s.log", w.prefix, day))
dstPath := srcPath + ".gz"
if _, err := os.Stat(dstPath); err == nil {
// 已压缩则删除明文(若仍存在)
_ = os.Remove(srcPath)
return nil
}
src, err := os.Open(srcPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer src.Close()
dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
if err != nil {
return err
}
gz := gzip.NewWriter(dst)
if _, err := io.Copy(gz, src); err != nil {
_ = gz.Close()
_ = dst.Close()
_ = os.Remove(dstPath)
return err
}
if err := gz.Close(); err != nil {
_ = dst.Close()
_ = os.Remove(dstPath)
return err
}
if err := dst.Close(); err != nil {
return err
}
return os.Remove(srcPath)
}
func (w *dailyWriter) purgeOlderThan(now time.Time) error {
// 按「日历天」比较:保留最近 retainDays 天(含当天),更早的 .log / .log.gz 删除。
today := time.Date(now.In(w.location).Year(), now.In(w.location).Month(), now.In(w.location).Day(), 0, 0, 0, 0, w.location)
cutoff := today.AddDate(0, 0, -w.retainDays) // day < cutoff 才删除;retainDays=2 且今天 12 号 → 删除 10 号之前
entries, err := os.ReadDir(w.dir)
if err != nil {
return err
}
prefix := w.prefix + "-"
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasPrefix(name, prefix) {
continue
}
// app-2026-07-11.log 或 app-2026-07-11.log.gz
rest := strings.TrimPrefix(name, prefix)
rest = strings.TrimSuffix(rest, ".gz")
rest = strings.TrimSuffix(rest, ".log")
day, err := time.ParseInLocation("2006-01-02", rest, w.location)
if err != nil {
continue
}
if day.Before(cutoff) {
_ = os.Remove(filepath.Join(w.dir, name))
}
}
return nil
}