package internal import ( "os" "path/filepath" "sync" "time" ) type Cutter struct { level string // 日志级别 layout string //时间格式 formats []string //自定义参数 director string //日志文件夹 retentionDay int //保留天数 file *os.File //文件 mutex *sync.RWMutex // 读写锁 } type CutterOption func(c *Cutter) // 设置时间格式 func CutterWithLayout(layout string) CutterOption { return func(c *Cutter) { c.layout = layout } } // 格式化参数 func CutterWithFormats(format ...string) CutterOption { return func(c *Cutter) { if len(format) > 0 { c.formats = format } } } // NewCutter 创建一个新的 Cutter 实例,用于管理日志文件的切割和保留。 // // 参数: // - directory: 日志文件存储的目录路径。 // - level: 日志级别,用于标识日志的严重程度。 // - retentionDay: 日志文件保留的天数,超过该天数的日志文件将被删除。 // - options: 可选的 CutterOption 函数,用于对 Cutter 实例进行额外的配置。 // // 返回值: // - *Cutter: 返回一个初始化后的 Cutter 实例。 func NewCutter(directory string, level string, retentionDay int, options ...CutterOption) *Cutter { // 初始化 Cutter 实例,设置日志级别、目录、保留天数以及互斥锁 rotate := &Cutter{ level: level, director: directory, retentionDay: retentionDay, mutex: new(sync.RWMutex), } // 应用所有传入的 CutterOption 配置函数 for i := 0; i < len(options); i++ { options[i](rotate) } return rotate } // Write 方法将给定的字节数据写入到日志文件中。该方法会确保日志文件的目录存在,并根据配置的格式生成文件名。 // 如果日志文件已经存在,数据将被追加到文件末尾。如果文件不存在,则会创建新文件。 // 该方法还会定期清理超过保留天数的日志文件夹。 // // 参数: // - bytes: 要写入的字节数据。 // // 返回值: // - n: 成功写入的字节数。 // - err: 如果发生错误,返回错误信息;否则返回 nil。 func (c *Cutter) Write(bytes []byte) (n int, err error) { // 加锁以确保并发安全 c.mutex.Lock() defer func() { // 在函数结束时关闭文件并释放锁 if c.file != nil { _ = c.file.Close() c.file = nil } c.mutex.Unlock() }() // 生成日志文件名 length := len(c.formats) values := make([]string, 0, 3+length) values = append(values, c.director) if c.layout != "" { values = append(values, time.Now().Format(c.layout)) } for i := 0; i < length; i++ { values = append(values, c.formats[i]) } values = append(values, c.level+".log") filename := filepath.Join(values...) // 确保日志文件所在的目录存在 directory := filepath.Dir(filename) err = os.MkdirAll(directory, os.ModePerm) if err != nil { return 0, nil } // 清理超过保留天数的日志文件夹 err = removeNDaysFolders(c.director, c.retentionDay) if err != nil { return 0, err } // 打开或创建日志文件,并追加写入数据 c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return 0, err } // 将数据写入文件并返回写入的字节数 return c.file.Write(bytes) } // Sync 方法用于将当前文件的内容同步到磁盘,确保所有缓冲区的数据都写入磁盘。 // 该方法在调用时会先获取互斥锁,以确保在同步过程中不会有其他操作干扰。 // 如果当前 Cutter 实例中的文件对象不为 nil,则调用文件对象的 Sync 方法进行同步操作。 // 如果文件对象为 nil,则直接返回 nil,表示无需同步。 // // 返回值: // - error: 如果同步过程中发生错误,则返回该错误;否则返回 nil。 func (c *Cutter) Sync() error { c.mutex.Lock() defer c.mutex.Unlock() // 如果文件对象存在,则调用其 Sync 方法进行同步 if c.file != nil { return c.file.Sync() } // 文件对象不存在,直接返回 nil return nil } // removeNDaysFolders 删除指定目录下,指定天数前的文件夹 func removeNDaysFolders(dir string, days int) error { if days <= 0 { return nil } cutoff := time.Now().AddDate(0, 0, -days) return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() && info.ModTime().Before(cutoff) && path != dir { err = os.RemoveAll(path) if err != nil { return err } } return nil }) }