Quellcode durchsuchen

feat(log): 用 DateRotator 替代 lumberjack.Logger 实现日志轮转

- 新增 DateRotator 结构体和相关方法,实现基于日期的的日志轮转功能
- 修改 log.go 中的日志配置,使用 DateRotator 替换原有的 lumberjack.Logger
- 优化了日志文件的命名方式和轮转逻辑,支持自定义日期格式
-移除了对 gopkg.in/natefinch/lumberjack.v2 的依赖
fusu vor 2 Monaten
Ursprung
Commit
9ce20ec2b5
3 geänderte Dateien mit 217 neuen und 11 gelöschten Zeilen
  1. 1 1
      go.mod
  2. 10 10
      pkg/log/log.go
  3. 206 0
      pkg/log/rotator.go

+ 1 - 1
go.mod

@@ -8,7 +8,6 @@ require (
 	github.com/AlekSi/pointer v1.2.0
 	github.com/DATA-DOG/go-sqlmock v1.5.2
 	github.com/PuerkitoBio/goquery v1.10.3
-	github.com/davecgh/go-spew v1.1.1
 	github.com/duke-git/lancet/v2 v2.3.5
 	github.com/gavv/httpexpect/v2 v2.16.0
 	github.com/gin-gonic/gin v1.9.1
@@ -46,6 +45,7 @@ require (
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
 	github.com/chenzhuoyu/iasm v0.9.0 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/fatih/color v1.15.0 // indirect

+ 10 - 10
pkg/log/log.go

@@ -7,7 +7,6 @@ import (
 	"github.com/spf13/viper"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
-	"gopkg.in/natefinch/lumberjack.v2"
 	"os"
 	"path/filepath"
 	"strings"
@@ -118,14 +117,15 @@ func NewServiceLog(conf *viper.Viper, serviceType ServiceType) *Logger {
 		level = zap.InfoLevel
 	}
 
-	// 日志轮转配置
-	hook := lumberjack.Logger{
-		Filename:   logPath,                        // 日志文件路径
-		MaxSize:    conf.GetInt("log.max_size"),    // 每个日志文件的最大大小单位:M
-		MaxBackups: conf.GetInt("log.max_backups"), // 日志文件最多保存的备份数
-		MaxAge:     conf.GetInt("log.max_age"),     // 文件最多保存的天数
-		Compress:   conf.GetBool("log.compress"),   // 是否压缩
-	}
+	// 日志轮转配置 - 使用DateRotator代替lumberjack.Logger
+	hook := NewDateRotator(
+		logPath,
+		conf.GetInt("log.max_size"),    // 每个日志文件的最大大小单位:M
+		conf.GetInt("log.max_backups"), // 日志文件最多保存的备份数
+		conf.GetInt("log.max_age"),     // 文件最多保存的天数
+		conf.GetBool("log.compress"),   // 是否压缩
+		logFormat,                      // 使用相同的日期格式
+	)
 
 	// 配置日志编码器
 	var encoder zapcore.Encoder
@@ -163,7 +163,7 @@ func NewServiceLog(conf *viper.Viper, serviceType ServiceType) *Logger {
 	// 创建日志核心
 	logOutput := zapcore.NewMultiWriteSyncer(
 		zapcore.AddSync(os.Stdout), // 同时输出到控制台
-		zapcore.AddSync(&hook),     // 和文件
+		zapcore.AddSync(hook),      // 和文件
 	)
 	core := zapcore.NewCore(encoder, logOutput, level)
 

+ 206 - 0
pkg/log/rotator.go

@@ -0,0 +1,206 @@
+package log
+
+import (
+	"os"
+	"path/filepath"
+	"sync"
+	"time"
+)
+
+// DateRotator 日期轮转日志
+type DateRotator struct {
+	// 基本配置
+	Filename   string // 日志文件路径模板,如 "./logs/app-%s.log"
+	MaxSize    int    // 单个文件最大大小,单位MB
+	MaxBackups int    // 最多保留多少个备份
+	MaxAge     int    // 文件最多保存多少天
+	Compress   bool   // 是否压缩
+	LogFormat  string // 日期格式,如 "2006-01-02"
+
+	// 内部状态
+	mu          sync.Mutex // 保护以下字段
+	file        *os.File   // 当前日志文件
+	currentDate string     // 当前日期
+	size        int64      // 当前文件大小
+}
+
+// NewDateRotator 创建一个基于日期轮转的日志器
+func NewDateRotator(filename string, maxSize, maxBackups, maxAge int, compress bool, logFormat string) *DateRotator {
+	if logFormat == "" {
+		logFormat = "2006-01-02" // 默认按天轮转
+	}
+
+	r := &DateRotator{
+		Filename:    filename,
+		MaxSize:     maxSize,
+		MaxBackups:  maxBackups,
+		MaxAge:      maxAge,
+		Compress:    compress,
+		LogFormat:   logFormat,
+		currentDate: time.Now().Format(logFormat),
+	}
+
+	return r
+}
+
+// Write 写入日志,同时检查是否需要轮转
+func (r *DateRotator) Write(p []byte) (n int, err error) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	// 检查日期是否变化
+	currentDate := time.Now().Format(r.LogFormat)
+	if r.file == nil || currentDate != r.currentDate {
+		if err := r.rotate(currentDate); err != nil {
+			return 0, err
+		}
+	}
+
+	// 检查文件大小是否超限
+	if r.size+int64(len(p)) >= int64(r.MaxSize*1024*1024) {
+		if err := r.rotate(currentDate); err != nil {
+			return 0, err
+		}
+	}
+
+	// 写入日志
+	n, err = r.file.Write(p)
+	r.size += int64(n)
+	return n, err
+}
+
+// Close 关闭当前日志文件
+func (r *DateRotator) Close() error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if r.file != nil {
+		err := r.file.Close()
+		r.file = nil
+		return err
+	}
+	return nil
+}
+
+// rotate 轮转日志文件
+func (r *DateRotator) rotate(newDate string) error {
+	// 关闭旧文件
+	if r.file != nil {
+		r.file.Close()
+		r.file = nil
+	}
+
+	// 更新日期
+	r.currentDate = newDate
+
+	// 直接使用传入的文件名,不再对文件名进行附加日期的处理
+	// 因为在log.go中已经对文件名进行了日期格式化
+	filename := r.Filename
+
+	// 确保目录存在
+	dir := filepath.Dir(filename)
+	if err := os.MkdirAll(dir, 0755); err != nil {
+		return err
+	}
+
+	// 打开新文件
+	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+	if err != nil {
+		return err
+	}
+
+	// 获取文件大小
+	info, err := f.Stat()
+	if err != nil {
+		f.Close()
+		return err
+	}
+
+	// 更新状态
+	r.file = f
+	r.size = info.Size()
+
+	// 清理旧文件(异步)
+	go r.cleanup()
+
+	return nil
+}
+
+// cleanup 清理旧日志文件
+func (r *DateRotator) cleanup() {
+	// 实现基于日期和数量的旧文件清理
+	if r.MaxAge <= 0 && r.MaxBackups <= 0 {
+		return
+	}
+
+	pattern := ""
+	dir := filepath.Dir(r.Filename)
+	base := filepath.Base(r.Filename)
+	ext := filepath.Ext(base)
+	prefix := base[:len(base)-len(ext)]
+
+	// 构造通配符模式
+	pattern = filepath.Join(dir, prefix+"*"+ext)
+
+	// 查找匹配的文件
+	matches, err := filepath.Glob(pattern)
+	if err != nil {
+		return
+	}
+
+	// 按修改时间排序
+	type fileInfo struct {
+		path    string
+		modTime time.Time
+	}
+	var files []fileInfo
+
+	for _, match := range matches {
+		info, err := os.Stat(match)
+		if err != nil {
+			continue
+		}
+		files = append(files, fileInfo{match, info.ModTime()})
+	}
+
+	// 按修改时间从新到旧排序
+	sort := func(i, j int) bool {
+		return files[i].modTime.After(files[j].modTime)
+	}
+	for i := 0; i < len(files)-1; i++ {
+		for j := i + 1; j < len(files); j++ {
+			if !sort(i, j) {
+				files[i], files[j] = files[j], files[i]
+			}
+		}
+	}
+
+	// 根据保留数量删除旧文件
+	if r.MaxBackups > 0 && len(files) > r.MaxBackups {
+		for i := r.MaxBackups; i < len(files); i++ {
+			os.Remove(files[i].path)
+		}
+		files = files[:r.MaxBackups]
+	}
+
+	// 根据保留天数删除旧文件
+	if r.MaxAge > 0 {
+		cutoff := time.Now().Add(-time.Duration(r.MaxAge) * 24 * time.Hour)
+		for _, f := range files {
+			if f.modTime.Before(cutoff) {
+				os.Remove(f.path)
+			}
+		}
+	}
+}
+
+// Sync 将缓存数据同步到磁盘
+func (r *DateRotator) Sync() error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if r.file != nil {
+		return r.file.Sync()
+	}
+	return nil
+}