|
@@ -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
|
|
|
|
+}
|