package utils import ( "log" "os" "path/filepath" "time" ) // DailyFileWriter 日志文件写入器 type DailyFileWriter struct { Dir string Freq string Prefix string File *os.File Current string Logger *log.Logger } // NewDailyFileWriter 创建每日日志写入器 func NewDailyFileWriter(dir, freq string, logger *log.Logger, prefix string) *DailyFileWriter { writer := &DailyFileWriter{ Dir: dir, Freq: freq, Prefix: prefix, Logger: logger, } writer.rotate() return writer } func (w *DailyFileWriter) getLogFileName() string { baseName := w.Prefix + "-" switch w.Freq { case "daily": return baseName + time.Now().Format("2006-01-02") + ".log" case "hourly": return baseName + time.Now().Format("2006-01-02-15") + ".log" default: // 默认按分钟(适合开发) return baseName + time.Now().Format("2006-01-02-15-04") + ".log" } } func (w *DailyFileWriter) rotate() { filename := w.getLogFileName() filePath := filepath.Join(w.Dir, filename) // 如果已经打开了相同的文件,则不需要重新打开 if w.Current == filePath { return } // 关闭当前文件(如果存在) if w.File != nil { w.File.Close() } // 确保目录存在(双重保障) if err := os.MkdirAll(w.Dir, 0755); err != nil { // 如果创建目录失败,尝试使用备用目录 if w.Dir != "logs" { w.Logger.Printf("⚠️ 无法创建日志目录 %s,尝试使用备用目录: %v", w.Dir, err) w.Dir = "logs" if err := os.MkdirAll(w.Dir, 0755); err != nil { w.Logger.Fatalf("❌ 无法创建备用日志目录: %v", err) } } else { w.Logger.Fatalf("❌ 无法创建日志目录: %v", err) } } // 打开文件(如果文件不存在则创建) file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { // 如果打开文件失败,尝试使用系统临时目录作为备选 fallbackPath := filepath.Join(os.TempDir(), "xk-websocket-logs", filename) w.Logger.Printf("⚠️ 无法打开日志文件 %s,尝试备选路径: %s (%v)", filePath, fallbackPath, err) // 确保备选目录存在 if err := os.MkdirAll(filepath.Dir(fallbackPath), 0755); err != nil { w.Logger.Fatalf("❌ 无法创建备选日志目录: %v", err) } file, err = os.OpenFile(fallbackPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { w.Logger.Fatalf("❌ 无法打开备选日志文件: %v", err) } } w.File = file w.Current = filePath } // Write 实现io.Writer接口 func (w *DailyFileWriter) Write(p []byte) (n int, err error) { // 检查是否需要轮转日志 needRotate := false switch w.Freq { case "daily": needRotate = time.Now().Day() != time.Now().Add(-time.Second).Day() case "hourly": needRotate = time.Now().Hour() != time.Now().Add(-time.Second).Hour() default: // 每分钟轮转(开发模式) needRotate = true } if needRotate { w.rotate() } return w.File.Write(p) } // Close 关闭日志文件 func (w *DailyFileWriter) Close() error { if w.File != nil { return w.File.Close() } return nil }