package gologger import ( "os" "path/filepath" "regexp" "sort" "strconv" "strings" "sync" "time" ) // FileAppender implements the LoggerAppender interface for file output. // It supports log rotation based on file size and age. // Example: // // appender := &FileAppender{ // formatter: NewSimpleFormatter(), // filePath: "app.log", // EnableRolling: true, // MaxSize: 1024 * 1024 * 10, // 10MB // } // appender.Append(event) // appender.Close() type FileAppender struct { formatter LogFormatter filePath string lchan chan LogEvent file *os.File stopChan chan struct{} mu sync.Mutex // 新增滚动相关字段 EnableRolling bool MaxSize int64 // 文件最大大小(字节) MaxAge int64 // 文件最大时间(秒) MaxCount int // 最大文件数量 currentFileSize int64 // 当前文件大小 createdAt int64 // 文件创建时间戳 } // Close implements LoggerAppender. func (f *FileAppender) Close() { //send stop signal f.stopChan <- struct{}{} } func (f *FileAppender) GetName() string { return "FileAppender:" + f.filePath } func (f *FileAppender) start() { f.lchan = make(chan LogEvent, 10) f.stopChan = make(chan struct{}) if f.file == nil || int(f.file.Fd()) == -1 { dirName := filepath.Dir(f.filePath) _, err := os.Stat(dirName) if err != nil && os.IsNotExist(err) { os.MkdirAll(dirName, 0755) } f.file, _ = os.OpenFile(f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) // 获取当前文件大小 if stat, err := f.file.Stat(); err == nil { f.currentFileSize = stat.Size() } // 记录文件创建时间 f.createdAt = time.Now().Unix() } go func() { for { select { case <-f.stopChan: f.mu.Lock() if f.file != nil { f.file.Close() } f.mu.Unlock() return case logEvent := <-f.lchan: f.mu.Lock() // 检查日志滚动 if f.EnableRolling { if f.checkAndRoll(logEvent) { f.mu.Unlock() continue } } logMsg := f.formatter(logEvent) n, err := f.file.WriteString(logMsg) if err == nil { f.currentFileSize += int64(n) } f.mu.Unlock() } } }() } // 新增日志滚动方法,返回是否进行了滚动 func (f *FileAppender) checkAndRoll(logEvent LogEvent) bool { // 按大小滚动 if f.MaxSize > 0 && f.currentFileSize >= f.MaxSize { f.rollFile(logEvent) return true } // 按时间滚动 if f.MaxAge > 0 && time.Now().Unix()-f.createdAt >= f.MaxAge { f.rollFile(logEvent) return true } return false } // 日志文件滚动 func (f *FileAppender) rollFile(logEvent LogEvent) { // 关闭当前文件 if f.file != nil { f.file.Close() } // 重命名旧文件 timestamp := time.Now().Format("20060102150405") newPath := f.filePath + "." + timestamp if err := os.Rename(f.filePath, newPath); err != nil { // 重命名失败,直接创建新文件 f.file, _ = os.OpenFile(f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) return } // 创建新文件 var err error f.file, err = os.OpenFile(f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return } // 重置状态 f.currentFileSize = 0 f.createdAt = time.Now().Unix() // 重新写入当前日志 logMsg := f.formatter(logEvent) if _, err := f.file.WriteString(logMsg); err == nil { f.currentFileSize += int64(len(logMsg)) } // 清理超过MaxCount的旧文件 if f.MaxCount > 0 { logDir := filepath.Dir(newPath) logFileBase := filepath.Base(f.filePath) files, _ := filepath.Glob(logDir + "/" + logFileBase + ".*") if len(files) > f.MaxCount { sort.Slice(files, func(i, j int) bool { return files[i] < files[j] }) for i := 0; i < len(files)-f.MaxCount; i++ { os.Remove(files[i]) } } } } func (f *FileAppender) Append(logEvent LogEvent) { f.lchan <- logEvent } func makeFileAppender(appenderConfig LogAppenderConfig) *LoggerAppender { logfile, ok := appenderConfig.Options["file"] if !ok { logfile = "default.log" } // 新增滚动配置参数 rollingEnabled := false if enable, ok := appenderConfig.Options["enableRolling"].(bool); ok { rollingEnabled = enable } maxSize := int64(0) if size, ok := appenderConfig.Options["maxSize"].(int64); ok { maxSize = size * 1024 * 1024 // 将兆转换为字节 } else if sizeStr, ok := appenderConfig.Options["maxSize"].(string); ok { // 解析带单位的大小配置(支持KB, MB, GB) var re = regexp.MustCompile(`(?i)^(\d+)([kmg]b)?$`) matches := re.FindStringSubmatch(sizeStr) if len(matches) > 0 { num, _ := strconv.ParseInt(matches[1], 10, 64) unit := "" if len(matches) > 2 { unit = matches[2] } switch unit { case "KB", "kb": maxSize = num * 1024 case "MB", "mb": maxSize = num * 1024 * 1024 case "GB", "gb": maxSize = num * 1024 * 1024 * 1024 default: maxSize = num * 1024 * 1024 // 默认按MB处理 } } } maxAge := int64(0) if ageStr, ok := appenderConfig.Options["maxAge"].(string); ok { // 解析带单位的时间配置 re := regexp.MustCompile(`^\d+[hd]?$`) if re.MatchString(ageStr) { // 提取数字部分和单位 var numStr strings.Builder unit := "" for _, c := range ageStr { if c >= '0' && c <= '9' { numStr.WriteRune(c) } else { unit = string(c) } } num, _ := strconv.ParseInt(numStr.String(), 10, 64) switch unit { case "h": maxAge = num * 3600 // 小时转秒 default: maxAge = num * 86400 // 默认按天转秒 } } } else if ageInt, ok := appenderConfig.Options["maxAge"].(int64); ok { maxAge = ageInt } var ret LoggerAppender = &FileAppender{ formatter: SelectFormatter(appenderConfig.Formatter), filePath: logfile.(string), EnableRolling: rollingEnabled, MaxSize: maxSize, MaxAge: maxAge, } ret.(*FileAppender).start() return &ret } func init() { RegistAppender("file", makeFileAppender) }