package common import ( "fmt" "os" "path/filepath" "runtime" "strings" "time" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" ) var ( Logger *logrus.Logger // 不同级别的日志文件writer infoLogger *lumberjack.Logger warnLogger *lumberjack.Logger errorLogger *lumberjack.Logger mainLogger *lumberjack.Logger ) // 日志级别常量 const ( LogLevelTrace = "trace" LogLevelDebug = "debug" LogLevelInfo = "info" LogLevelWarn = "warn" LogLevelError = "error" LogLevelFatal = "fatal" LogLevelPanic = "panic" ) // LogConfig 日志配置结构体 type LogConfig struct { LogDir string // 日志目录 LogLevel string // 日志级别 MaxSize int // 每个日志文件最大大小(MB) MaxBackups int // 保留的旧日志文件数量 MaxAge int // 保留日志文件的最大天数 Compress bool // 是否压缩旧日志文件 LogModules []string // 需要记录日志的模块 EnableCaller bool // 是否显示调用者信息 } // InitLogger 初始化日志系统 func InitLogger(config *LogConfig) error { if config == nil { // 使用默认配置 config = getDefaultLogConfig() } // 确保日志目录存在 if err := os.MkdirAll(config.LogDir, 0755); err != nil { return fmt.Errorf("创建日志目录失败: %v", err) } // 创建logrus实例 Logger = logrus.New() // 设置日志级别 level, err := logrus.ParseLevel(config.LogLevel) if err != nil { level = logrus.InfoLevel } Logger.SetLevel(level) // 设置日志格式 Logger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05", }) // 设置调用者信息 Logger.SetReportCaller(config.EnableCaller) // 设置日志输出 if err := setLogOutput(config); err != nil { return err } // 记录系统启动日志 Logger.WithFields(logrus.Fields{ "event": "system_startup", "pid": os.Getpid(), }).Info("SCADA培训系统启动") return nil } // setLogOutput 设置日志输出 func setLogOutput(config *LogConfig) error { // 创建不同级别的日志文件 infoLogPath := filepath.Join(config.LogDir, "info.log") warnLogPath := filepath.Join(config.LogDir, "warn.log") errorLogPath := filepath.Join(config.LogDir, "error.log") // 创建主日志文件 mainLogPath := filepath.Join(config.LogDir, "app.log") // 为主日志创建writer mainWriter := &lumberjack.Logger{ Filename: mainLogPath, MaxSize: config.MaxSize, MaxBackups: config.MaxBackups, MaxAge: config.MaxAge, Compress: config.Compress, } // 同时输出到控制台和文件 // 使用MultiWriter同时写入控制台和文件 Logger.SetOutput(mainWriter) // 保存writer引用,用于后续使用 mainLogger = mainWriter infoLogger = &lumberjack.Logger{ Filename: infoLogPath, MaxSize: config.MaxSize, MaxBackups: config.MaxBackups, MaxAge: config.MaxAge, Compress: config.Compress, } warnLogger = &lumberjack.Logger{ Filename: warnLogPath, MaxSize: config.MaxSize, MaxBackups: config.MaxBackups, MaxAge: config.MaxAge, Compress: config.Compress, } errorLogger = &lumberjack.Logger{ Filename: errorLogPath, MaxSize: config.MaxSize, MaxBackups: config.MaxBackups, MaxAge: config.MaxAge, Compress: config.Compress, } // 启动日志清理协程 go startLogCleaner(config) return nil } // startLogCleaner 启动日志清理协程 func startLogCleaner(config *LogConfig) { // 每天凌晨2点执行日志清理 ticker := time.NewTicker(24 * time.Hour) defer ticker.Stop() // 计算到下一个凌晨2点的时间 now := time.Now() next := time.Date(now.Year(), now.Month(), now.Day(), 2, 0, 0, 0, now.Location()) if next.Before(now) { next = next.Add(24 * time.Hour) } initialDelay := next.Sub(now) // 先等待到下一个凌晨2点 time.Sleep(initialDelay) // 执行日志清理 cleanOldLogs(config) for range ticker.C { cleanOldLogs(config) } } // cleanOldLogs 清理旧的日志文件 func cleanOldLogs(config *LogConfig) { if config.MaxAge <= 0 { return // 不启用自动清理 } // 清理info级别的日志 cleanOldLogsByPattern(config.LogDir, "info-*.log.gz", config.MaxAge) cleanOldLogsByPattern(config.LogDir, "info-*.log", config.MaxAge) // 清理warn级别的日志 cleanOldLogsByPattern(config.LogDir, "warn-*.log.gz", config.MaxAge) cleanOldLogsByPattern(config.LogDir, "warn-*.log", config.MaxAge) // 清理error级别的日志 cleanOldLogsByPattern(config.LogDir, "error-*.log.gz", config.MaxAge) cleanOldLogsByPattern(config.LogDir, "error-*.log", config.MaxAge) // 记录日志清理操作 Logger.WithFields(logrus.Fields{ "event": "log_cleanup", "max_age_days": config.MaxAge, }).Info("执行日志清理操作") } // cleanOldLogsByPattern 根据模式清理旧的日志文件 func cleanOldLogsByPattern(logDir, pattern string, maxAge int) { matches, err := filepath.Glob(filepath.Join(logDir, pattern)) if err != nil { Logger.WithFields(logrus.Fields{ "event": "log_cleanup_error", "error": err.Error(), "pattern": pattern, }).Error("获取日志文件列表失败") return } cutoffTime := time.Now().AddDate(0, 0, -maxAge) for _, filePath := range matches { fileInfo, err := os.Stat(filePath) if err != nil { continue } if fileInfo.ModTime().Before(cutoffTime) { if err := os.Remove(filePath); err != nil { Logger.WithFields(logrus.Fields{ "event": "log_file_delete_error", "error": err.Error(), "file": filePath, }).Error("删除旧日志文件失败") } else { Logger.WithFields(logrus.Fields{ "event": "log_file_deleted", "file": filePath, "mod_time": fileInfo.ModTime().Format("2006-01-02 15:04:05"), }).Debug("已删除旧日志文件") } } } } // SetLogLevel 动态设置日志级别 func SetLogLevel(level string) error { if Logger == nil { return fmt.Errorf("日志系统未初始化") } logLevel, err := logrus.ParseLevel(level) if err != nil { return fmt.Errorf("无效的日志级别: %s", level) } Logger.SetLevel(logLevel) Logger.WithFields(logrus.Fields{ "event": "log_level_changed", "new_level": level, }).Info("日志级别已更新") return nil } // GetLogFilesInfo 获取日志文件信息 func GetLogFilesInfo() ([]map[string]interface{}, error) { if Logger == nil { return nil, fmt.Errorf("日志系统未初始化") } // 获取log目录 logDir := filepath.Dir(getDefaultLogConfig().LogDir) // 查找所有日志文件 patterns := []string{"info*.log", "warn*.log", "error*.log"} var result []map[string]interface{} for _, pattern := range patterns { matches, err := filepath.Glob(filepath.Join(logDir, pattern)) if err != nil { continue } for _, filePath := range matches { fileInfo, err := os.Stat(filePath) if err != nil { continue } fileInfoMap := map[string]interface{}{ "name": filepath.Base(filePath), "path": filePath, "size": fileInfo.Size(), "mod_time": fileInfo.ModTime().Format("2006-01-02 15:04:05"), "is_compressed": strings.HasSuffix(filePath, ".gz"), } result = append(result, fileInfoMap) } } return result, nil } // getDefaultLogConfig 获取默认日志配置 func getDefaultLogConfig() *LogConfig { exePath, err := os.Executable() if err != nil { exePath = "." } exeDir := filepath.Dir(exePath) return &LogConfig{ LogDir: filepath.Join(exeDir, "log"), LogLevel: "info", MaxSize: 100, // 100MB MaxBackups: 30, // 保留30个旧文件 MaxAge: 90, // 保留90天 Compress: true, EnableCaller: true, } } // GetLogger 获取日志实例 func GetLogger() *logrus.Logger { if Logger == nil { // 如果未初始化,使用默认配置初始化 InitLogger(nil) } return Logger } // LogWithFields 带字段的日志记录 func LogWithFields(fields logrus.Fields) *logrus.Entry { return GetLogger().WithFields(fields) } // LogInfo 记录信息级别日志 func LogInfo(msg string, fields ...logrus.Fields) { logger := GetLogger() // 写入主日志文件 if len(fields) > 0 { entry := logger.WithFields(fields[0]) entry.Info(msg) // 同时写入info级别的日志文件 writeToLevelFile("info", msg, fields[0]) } else { logger.Info(msg) // 同时写入info级别的日志文件 writeToLevelFile("info", msg, nil) } } // LogWarn 记录警告级别日志 func LogWarn(msg string, fields ...logrus.Fields) { logger := GetLogger() // 写入主日志文件 if len(fields) > 0 { entry := logger.WithFields(fields[0]) entry.Warn(msg) // 同时写入warn级别的日志文件 writeToLevelFile("warn", msg, fields[0]) } else { logger.Warn(msg) // 同时写入warn级别的日志文件 writeToLevelFile("warn", msg, nil) } } // LogError 记录错误级别日志 func LogError(msg string, err error, fields ...logrus.Fields) { logger := GetLogger() logFields := logrus.Fields{} if err != nil { logFields["error"] = err.Error() } if len(fields) > 0 { for k, v := range fields[0] { logFields[k] = v } } // 写入主日志文件 entry := logger.WithFields(logFields) entry.Error(msg) // 同时写入error级别的日志文件 writeToLevelFile("error", msg, logFields) } // LogDebug 记录调试级别日志 func LogDebug(msg string, fields ...logrus.Fields) { logger := GetLogger() // 写入主日志文件(debug级别不单独记录到debug.log) if len(fields) > 0 { entry := logger.WithFields(fields[0]) entry.Debug(msg) } else { logger.Debug(msg) } } // LogTrace 记录跟踪级别日志 func LogTrace(msg string, fields ...logrus.Fields) { logger := GetLogger() // 写入主日志文件(trace级别不单独记录到trace.log) if len(fields) > 0 { entry := logger.WithFields(fields[0]) entry.Trace(msg) } else { logger.Trace(msg) } } // writeToLevelFile 将日志写入对应的级别日志文件 func writeToLevelFile(level, msg string, fields logrus.Fields) { var writer *lumberjack.Logger switch level { case "info": writer = infoLogger case "warn": writer = warnLogger case "error": writer = errorLogger default: return } if writer == nil { return } // 创建文件专用的logger fileLogger := logrus.New() fileLogger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05", }) fileLogger.SetOutput(writer) // 根据级别设置日志级别 switch level { case "info": fileLogger.SetLevel(logrus.InfoLevel) if fields != nil { fileLogger.WithFields(fields).Info(msg) } else { fileLogger.Info(msg) } case "warn": fileLogger.SetLevel(logrus.WarnLevel) if fields != nil { fileLogger.WithFields(fields).Warn(msg) } else { fileLogger.Warn(msg) } case "error": fileLogger.SetLevel(logrus.ErrorLevel) if fields != nil { fileLogger.WithFields(fields).Error(msg) } else { fileLogger.Error(msg) } } } // LogRequest 记录HTTP请求日志 func LogRequest(method, path, ip, userAgent string, statusCode int, duration time.Duration) { GetLogger().WithFields(logrus.Fields{ "event": "http_request", "method": method, "path": path, "ip": ip, "user_agent": userAgent, "status": statusCode, "duration": duration.Milliseconds(), }).Info("HTTP请求") } // LogResponse 记录HTTP响应日志 func LogResponse(data interface{}, fields ...logrus.Fields) { logFields := logrus.Fields{ "event": "http_response", "data": data, } if len(fields) > 0 { for k, v := range fields[0] { logFields[k] = v } } GetLogger().WithFields(logFields).Info("HTTP响应") } // LogDBOperation 记录数据库操作日志 func LogDBOperation(operation, table string, duration time.Duration, fields ...logrus.Fields) { logFields := logrus.Fields{ "event": "db_operation", "operation": operation, "table": table, "duration": duration.Milliseconds(), } if len(fields) > 0 { for k, v := range fields[0] { logFields[k] = v } } GetLogger().WithFields(logFields).Info("数据库操作") } // LogUserAction 记录用户操作日志 func LogUserAction(userID, userName, action, module string, fields ...logrus.Fields) { logFields := logrus.Fields{ "event": "user_action", "user_id": userID, "user_name": userName, "action": action, "module": module, } if len(fields) > 0 { for k, v := range fields[0] { logFields[k] = v } } GetLogger().WithFields(logFields).Info("用户操作") } // LogSystemError 记录系统错误日志 func LogSystemError(component, operation string, err error, fields ...logrus.Fields) { logFields := logrus.Fields{ "event": "system_error", "component": component, "operation": operation, "error": err.Error(), } // 添加调用者信息 if pc, file, line, ok := runtime.Caller(1); ok { funcName := runtime.FuncForPC(pc).Name() logFields["caller"] = fmt.Sprintf("%s:%d:%s", filepath.Base(file), line, funcName) } if len(fields) > 0 { for k, v := range fields[0] { logFields[k] = v } } GetLogger().WithFields(logFields).Error("系统错误") } // LogBusinessEvent 记录业务事件日志 func LogBusinessEvent(eventType, description string, fields ...logrus.Fields) { logFields := logrus.Fields{ "event": "business_event", "event_type": eventType, "description": description, } if len(fields) > 0 { for k, v := range fields[0] { logFields[k] = v } } GetLogger().WithFields(logFields).Info("业务事件") } // CloseLogger 关闭日志系统 func CloseLogger() { if Logger != nil { Logger.Info("SCADA培训系统关闭") } }