package logger import ( "fmt" "io" "log" "os" "path/filepath" "strings" "sync" "time" ) // LogEntry 定义日志条目结构体,实际实现在operation_logger.go // 这里保留类型定义以保持向后兼容性 type LogEntry struct { ID int64 `json:"id"` Time string `json:"time"` Source string `json:"source"` Type string `json:"type"` Content string `json:"content"` Duration int64 `json:"duration"` } // LogLevel 定义日志级别 type LogLevel int const ( LevelDebug LogLevel = iota LevelInfo LevelWarning LevelError ) // Logger 日志器结构体 type Logger struct { Logger *log.Logger LogLevel LogLevel LogEnabled bool mu sync.Mutex // 日志文件相关字段 logFile *os.File logDir string exeName string maxFileSize int64 currentSize int64 Lock *sync.Mutex } // GetLogDir 获取日志目录路径 func (l *Logger) GetLogDir() string { return l.logDir } // 确保日志文件大小不超过限制 func ensureLogFileSize(logDir string, baseLogName string) string { // 尝试最多10次找到一个可用的文件名 for i := 0; i < 10; i++ { var logFileName string if i == 0 { logFileName = baseLogName } else { logFileName = fmt.Sprintf("%s_%d.txt", baseLogName[:len(baseLogName)-4], i) } logFilePath := filepath.Join(logDir, logFileName) // 检查文件是否存在以及大小 fileInfo, err := os.Stat(logFilePath) if err != nil || fileInfo.Size() < 5*1024*1024 { // 5MB return logFilePath } } // 如果所有尝试都失败,返回一个默认文件名 return filepath.Join(logDir, fmt.Sprintf("%s_default.txt", baseLogName[:len(baseLogName)-4])) } // NewLogger 创建一个新的日志器 func NewLogger(exeName string, enabled bool, level LogLevel) (*Logger, error) { // 获取程序路径 exePath, err := os.Executable() var logDir string if err != nil { // 如果获取程序路径失败,使用临时目录作为备选 logDir = filepath.Join(os.TempDir(), fmt.Sprintf("%s_logs", exeName)) } else { // 使用程序所在目录的Log子目录 exeDir := filepath.Dir(exePath) logDir = filepath.Join(exeDir, "Log") } // 创建基础日志目录 if err := os.MkdirAll(logDir, 0755); err != nil { return nil, fmt.Errorf("无法创建日志目录: %v", err) } // 获取当前日期,用于创建日期子目录(格式:2006-01-02) today := time.Now().Format("2006-01-02") dateDir := filepath.Join(logDir, today) // 创建日期子目录 if err := os.MkdirAll(dateDir, 0755); err != nil { return nil, fmt.Errorf("无法创建日期子目录: %v", err) } // 生成当前日期时间的日志文件名(格式:appName_YYYYMMDD_HHmmss.txt) timeStr := time.Now().Format("20060102_150405") baseLogName := fmt.Sprintf("%s_%s.txt", exeName, timeStr) logFilePath := filepath.Join(dateDir, baseLogName) // 确保新文件不会超过大小限制 logFilePath = ensureLogFileSize(dateDir, baseLogName) // 打开日志文件 logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, fmt.Errorf("无法打开日志文件: %v", err) } // 获取文件信息以初始化当前大小 fileInfo, err := logFile.Stat() if err != nil { return nil, fmt.Errorf("无法获取文件信息: %v", err) } // 创建logger logger := log.New(logFile, "", log.LstdFlags) // 返回日志器 return &Logger{ Logger: logger, LogLevel: level, LogEnabled: enabled, logFile: logFile, logDir: logDir, exeName: exeName, maxFileSize: 5 * 1024 * 1024, // 5MB currentSize: fileInfo.Size(), Lock: &sync.Mutex{}, }, nil } // Close 关闭日志文件 func (l *Logger) Close() { l.Lock.Lock() defer l.Lock.Unlock() if l.logFile != nil { l.logFile.Close() l.logFile = nil } } // Debug 记录调试日志 func (l *Logger) Debug(format string, v ...interface{}) { if l == nil { return } if l.LogEnabled && l.LogLevel <= LevelDebug { l.log("[调试]", format, v...) } } // Info 记录信息日志 func (l *Logger) Info(format string, v ...interface{}) { if l == nil { return } if l.LogEnabled && l.LogLevel <= LevelInfo { l.log("[信息]", format, v...) } } // Warn 记录警告日志 func (l *Logger) Warn(format string, v ...interface{}) { if l == nil { return } if l.LogEnabled && l.LogLevel <= LevelWarning { l.log("[警告]", format, v...) } } // Error 记录错误日志 func (l *Logger) Error(format string, v ...interface{}) { if l == nil { return } if l.LogEnabled && l.LogLevel <= LevelError { l.log("[错误]", format, v...) } } // log 实际的日志记录函数 func (l *Logger) log(level string, format string, v ...interface{}) { if l == nil || l.Lock == nil || l.Logger == nil { return } l.Lock.Lock() defer l.Lock.Unlock() // 格式化日志消息 message := fmt.Sprintf(format, v...) fullMessage := fmt.Sprintf("%s %s", level, message) // 写入日志 l.Logger.Println(fullMessage) // 更新当前文件大小估计 // 注意:这只是估计值,实际大小可能不同 l.currentSize += int64(len(fullMessage) + 20) // 20是为时间戳等额外内容预留的空间 // 检查是否需要创建新文件 if l.LogEnabled && l.currentSize >= l.maxFileSize { // 关闭当前文件 if l.logFile != nil { l.logFile.Close() } // 创建新的日志文件 today := time.Now().Format("2006-01-02") dateDir := filepath.Join(l.logDir, today) // 创建日期子目录 if err := os.MkdirAll(dateDir, 0755); err != nil { fmt.Fprintf(os.Stderr, "无法创建日期子目录: %v\n", err) dateDir = l.logDir // 如果创建失败,回退到基础日志目录 } timeStr := time.Now().Format("20060102_150405") baseLogName := fmt.Sprintf("%s_%s.txt", l.exeName, timeStr) logFilePath := filepath.Join(dateDir, baseLogName) // 确保新文件不会超过大小限制 logFilePath = ensureLogFileSize(dateDir, baseLogName) // 打开新的日志文件 var err error l.logFile, err = os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { // 重新设置logger的输出 l.Logger.SetOutput(l.logFile) // 重置当前大小 l.currentSize = 0 } else { // 如果无法打开新文件,输出错误到标准错误 fmt.Fprintf(os.Stderr, "无法创建新的日志文件: %v\n", err) } } } // SetEnabled 设置日志总开关 func (l *Logger) SetEnabled(enabled bool) { l.Lock.Lock() defer l.Lock.Unlock() if l.LogEnabled == enabled { return } l.LogEnabled = enabled if enabled { // 如果启用日志,但文件已关闭,重新打开 if l.logFile == nil { today := time.Now().Format("2006-01-02") dateDir := filepath.Join(l.logDir, today) // 创建日期子目录 if err := os.MkdirAll(dateDir, 0755); err != nil { fmt.Fprintf(os.Stderr, "无法创建日期子目录: %v\n", err) dateDir = l.logDir // 如果创建失败,回退到基础日志目录 } timeStr := time.Now().Format("20060102_150405") baseLogName := fmt.Sprintf("%s_%s.txt", l.exeName, timeStr) logFilePath := filepath.Join(dateDir, baseLogName) // 确保新文件不会超过大小限制 logFilePath = ensureLogFileSize(dateDir, baseLogName) // 打开日志文件 var err error l.logFile, err = os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { // 设置logger的输出 l.Logger.SetOutput(l.logFile) // 获取文件当前大小 fileInfo, err := l.logFile.Stat() if err == nil { l.currentSize = fileInfo.Size() } } else { // 如果无法打开文件,输出错误到标准错误 fmt.Fprintf(os.Stderr, "无法打开日志文件: %v\n", err) } } } else { // 如果禁用日志,将输出重定向到/dev/null l.Logger.SetOutput(io.Discard) } } // SetLogLevel 设置日志级别 func (l *Logger) SetLogLevel(level LogLevel) { l.Lock.Lock() defer l.Lock.Unlock() l.LogLevel = level } // CleanOldLogs 清理指定天数之前的旧日志文件 func CleanOldLogs(logDir string, daysToKeep int) error { if daysToKeep <= 0 { daysToKeep = 30 // 默认保留30天 } cutoffTime := time.Now().AddDate(0, 0, -daysToKeep) // 遍历日志目录下的所有子目录和文件 err := filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error { if err != nil { return nil // 跳过错误,继续处理 } // 跳过目录 if info.IsDir() { return nil } // 只处理.txt和.log文件 ext := strings.ToLower(filepath.Ext(path)) if ext != ".txt" && ext != ".log" { return nil } // 检查文件修改时间 if info.ModTime().Before(cutoffTime) { // 删除旧日志文件 if removeErr := os.Remove(path); removeErr != nil { // 记录错误但不中断清理过程 fmt.Printf("删除旧日志文件失败: %s, 错误: %v\n", path, removeErr) } else { fmt.Printf("已删除旧日志文件: %s\n", path) } } return nil }) return err } // StartLogCleanupScheduler 启动日志清理定时器 func StartLogCleanupScheduler(logDir string, daysToKeep int, checkInterval time.Duration) { if daysToKeep <= 0 { daysToKeep = 30 } if checkInterval <= 0 { checkInterval = 24 * time.Hour // 默认每天检查一次 } go func() { ticker := time.NewTicker(checkInterval) defer ticker.Stop() // 立即执行一次清理 if err := CleanOldLogs(logDir, daysToKeep); err != nil { fmt.Printf("日志清理失败: %v\n", err) } // 定时清理 for range ticker.C { if err := CleanOldLogs(logDir, daysToKeep); err != nil { fmt.Printf("日志清理失败: %v\n", err) } } }() }