package logger import ( "fmt" "io" "log" "os" "path/filepath" "strings" "sync" "time" ) type Level int const ( LevelDebug Level = iota LevelInfo LevelWarn LevelError ) const ( defaultDir = "./logs" ) var ( mu sync.RWMutex currentLevel = LevelInfo debugLog = log.New(os.Stdout, "[DEBUG] ", log.Ldate|log.Ltime) infoLog = log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime) warnLog = log.New(os.Stderr, "[WARN] ", log.Ldate|log.Ltime) errorLog = log.New(os.Stderr, "[ERROR] ", log.Ldate|log.Ltime) logFile *rotatingFileWriter ) type rotatingFileWriter struct { mu sync.Mutex file *os.File path string logDate string // 当前日志日期 YYYY-MM-DD } func (w *rotatingFileWriter) Write(p []byte) (n int, err error) { w.mu.Lock() defer w.mu.Unlock() today := time.Now().Format("2006-01-02") if today != w.logDate { if err := w.rotate(today); err != nil { return 0, err } } return w.file.Write(p) } func (w *rotatingFileWriter) rotate(today string) error { if w.logDate != "" { w.file.Close() backup := w.path + "." + w.logDate os.Rename(w.path, backup) } f, err := os.OpenFile(w.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } w.file = f w.logDate = today return nil } func (w *rotatingFileWriter) Close() error { w.mu.Lock() defer w.mu.Unlock() if w.file != nil { return w.file.Close() } return nil } func SetLevel(l Level) { mu.Lock() defer mu.Unlock() currentLevel = l } func GetLevel() Level { mu.RLock() defer mu.RUnlock() return currentLevel } func ParseLevel(s string) Level { switch strings.ToLower(s) { case "debug": return LevelDebug case "info": return LevelInfo case "warn", "warning": return LevelWarn case "error": return LevelError default: return LevelInfo } } // InitFileLog 初始化文件日志。logPath 为空时默认 ./logs/monitor.log。 // 按天轮转,日志同时输出到控制台和文件。 func InitFileLog(logPath string) error { mu.Lock() defer mu.Unlock() if logPath == "" { logPath = defaultDir } fi, err := os.Stat(logPath) if err != nil || fi.IsDir() { logPath = filepath.Join(logPath, "monitor.log") } dir := filepath.Dir(logPath) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建日志目录失败: %w", err) } f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("打开日志文件失败: %w", err) } logFile = &rotatingFileWriter{ file: f, path: logPath, logDate: time.Now().Format("2006-01-02"), } debugLog.SetOutput(io.MultiWriter(os.Stdout, logFile)) infoLog.SetOutput(io.MultiWriter(os.Stdout, logFile)) warnLog.SetOutput(io.MultiWriter(os.Stderr, logFile)) errorLog.SetOutput(io.MultiWriter(os.Stderr, logFile)) return nil } func Close() { mu.Lock() defer mu.Unlock() if logFile != nil { logFile.Close() logFile = nil } } func Debug(format string, v ...interface{}) { mu.RLock() l := currentLevel mu.RUnlock() if LevelDebug >= l { debugLog.Output(2, fmt.Sprintf(format, v...)) } } func Info(format string, v ...interface{}) { mu.RLock() l := currentLevel mu.RUnlock() if LevelInfo >= l { infoLog.Output(2, fmt.Sprintf(format, v...)) } } func Warn(format string, v ...interface{}) { mu.RLock() l := currentLevel mu.RUnlock() if LevelWarn >= l { warnLog.Output(2, fmt.Sprintf(format, v...)) } } func Error(format string, v ...interface{}) { mu.RLock() l := currentLevel mu.RUnlock() if LevelError >= l { errorLog.Output(2, fmt.Sprintf(format, v...)) } }