123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- package log
- import (
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
- "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
- // 根据文件名格式更新日期部分
- var filename string
- if strings.Contains(r.Filename, "%s") {
- // 如果文件名中有格式化占位符,直接使用新日期替换
- filename = fmt.Sprintf(r.Filename, newDate)
- } else {
- // 如果没有占位符,则检查文件名是否已经包含日期格式
- ext := filepath.Ext(r.Filename)
- base := r.Filename[:len(r.Filename)-len(ext)]
- // 如果文件名中已经有日期格式(如api-2025-05-26.log),则替换日期部分
- datePattern := regexp.MustCompile(`-\d{4}-\d{2}-\d{2}`)
- if datePattern.MatchString(base) {
- base = datePattern.ReplaceAllString(base, "-"+newDate)
- filename = base + ext
- } else {
- // 否则在扩展名前添加日期
- filename = base + "-" + newDate + ext
- }
- }
- // 确保目录存在
- 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
- }
|