diff --git a/db/connection.go b/db/connection.go index 970cb74..2cdcec5 100644 --- a/db/connection.go +++ b/db/connection.go @@ -209,7 +209,15 @@ func createIndexes(db *gorm.DB) { db.Exec("CREATE INDEX IF NOT EXISTS idx_resource_tags_resource_id ON resource_tags(resource_id)") db.Exec("CREATE INDEX IF NOT EXISTS idx_resource_tags_tag_id ON resource_tags(tag_id)") - utils.Info("数据库索引创建完成(已移除全文搜索索引,准备使用Meilisearch)") + // API访问日志表索引 - 高性能查询优化 + db.Exec("CREATE INDEX IF NOT EXISTS idx_api_access_logs_created_at ON api_access_logs(created_at DESC)") + db.Exec("CREATE INDEX IF NOT EXISTS idx_api_access_logs_endpoint_status ON api_access_logs(endpoint, response_status)") + db.Exec("CREATE INDEX IF NOT EXISTS idx_api_access_logs_ip_created ON api_access_logs(ip, created_at DESC)") + db.Exec("CREATE INDEX IF NOT EXISTS idx_api_access_logs_method_endpoint ON api_access_logs(method, endpoint)") + db.Exec("CREATE INDEX IF NOT EXISTS idx_api_access_logs_response_time ON api_access_logs(processing_time)") + db.Exec("CREATE INDEX IF NOT EXISTS idx_api_access_logs_error_logs ON api_access_logs(response_status, created_at DESC) WHERE response_status >= 400") + + utils.Info("数据库索引创建完成(已移除全文搜索索引,准备使用Meilisearch,新增API访问日志性能索引)") } // insertDefaultDataIfEmpty 只在数据库为空时插入默认数据 diff --git a/handlers/public_api_handler.go b/handlers/public_api_handler.go index d8fb1ef..2d95a9d 100644 --- a/handlers/public_api_handler.go +++ b/handlers/public_api_handler.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/json" "strconv" "strings" "time" @@ -91,30 +92,9 @@ func (h *PublicAPIHandler) logAPIAccess(c *gin.Context, startTime time.Time, pro } } - // 异步记录日志,避免影响API响应时间 - go func() { - defer func() { - if r := recover(); r != nil { - utils.Error("记录API访问日志时发生panic: %v", r) - } - }() - - err := repoManager.APIAccessLogRepository.RecordAccess( - ip, - userAgent, - endpoint, - method, - requestParams, - c.Writer.Status(), - responseData, - processCount, - errorMessage, - processingTime, - ) - if err != nil { - utils.Error("记录API访问日志失败: %v", err) - } - }() + // 记录API访问日志 - 使用简单日志记录 + h.recordAPIAccessToDB(ip, userAgent, endpoint, method, requestParams, + c.Writer.Status(), responseData, processCount, errorMessage, processingTime) } // AddBatchResources godoc @@ -466,3 +446,49 @@ func (h *PublicAPIHandler) GetHotDramas(c *gin.Context) { h.logAPIAccess(c, startTime, len(hotDramaResponses), responseData, "") SuccessResponse(c, responseData) } + +// recordAPIAccessToDB 记录API访问日志到数据库 +func (h *PublicAPIHandler) recordAPIAccessToDB(ip, userAgent, endpoint, method string, + requestParams interface{}, responseStatus int, responseData interface{}, + processCount int, errorMessage string, processingTime int64) { + + // 只记录重要的API访问(有错误或处理时间较长的) + if errorMessage == "" && processingTime < 1000 && responseStatus < 400 { + return // 跳过正常的快速请求 + } + + // 转换参数为JSON字符串 + var requestParamsStr, responseDataStr string + if requestParams != nil { + if jsonBytes, err := json.Marshal(requestParams); err == nil { + requestParamsStr = string(jsonBytes) + } + } + if responseData != nil { + if jsonBytes, err := json.Marshal(responseData); err == nil { + responseDataStr = string(jsonBytes) + } + } + + // 创建日志记录 + logEntry := &entity.APIAccessLog{ + IP: ip, + UserAgent: userAgent, + Endpoint: endpoint, + Method: method, + RequestParams: requestParamsStr, + ResponseStatus: responseStatus, + ResponseData: responseDataStr, + ProcessCount: processCount, + ErrorMessage: errorMessage, + ProcessingTime: processingTime, + } + + // 异步保存到数据库(避免影响API性能) + go func() { + if err := repoManager.APIAccessLogRepository.Create(logEntry); err != nil { + // 记录失败只输出到系统日志,不影响API + utils.Error("保存API访问日志失败: %v", err) + } + }() +} diff --git a/handlers/system_config_handler.go b/handlers/system_config_handler.go index 474f7f9..76419c4 100644 --- a/handlers/system_config_handler.go +++ b/handlers/system_config_handler.go @@ -145,27 +145,26 @@ func UpdateSystemConfig(c *gin.Context) { utils.Info("当前配置数量: %d", len(currentConfigs)) } - // 验证参数 - 只验证提交的字段 - utils.Info("开始验证参数") + // 验证参数 - 只验证提交的字段,仅在验证失败时记录日志 if req.SiteTitle != nil { - utils.Info("验证SiteTitle: '%s', 长度: %d", *req.SiteTitle, len(*req.SiteTitle)) if len(*req.SiteTitle) < 1 || len(*req.SiteTitle) > 100 { + utils.Warn("配置验证失败 - SiteTitle长度无效: %d", len(*req.SiteTitle)) ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest) return } } if req.AutoProcessInterval != nil { - utils.Info("验证AutoProcessInterval: %d", *req.AutoProcessInterval) if *req.AutoProcessInterval < 1 || *req.AutoProcessInterval > 1440 { + utils.Warn("配置验证失败 - AutoProcessInterval超出范围: %d", *req.AutoProcessInterval) ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest) return } } if req.PageSize != nil { - utils.Info("验证PageSize: %d", *req.PageSize) if *req.PageSize < 10 || *req.PageSize > 500 { + utils.Warn("配置验证失败 - PageSize超出范围: %d", *req.PageSize) ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest) return } @@ -173,16 +172,16 @@ func UpdateSystemConfig(c *gin.Context) { // 验证自动转存配置 if req.AutoTransferLimitDays != nil { - utils.Info("验证AutoTransferLimitDays: %d", *req.AutoTransferLimitDays) if *req.AutoTransferLimitDays < 0 || *req.AutoTransferLimitDays > 365 { + utils.Warn("配置验证失败 - AutoTransferLimitDays超出范围: %d", *req.AutoTransferLimitDays) ErrorResponse(c, "自动转存限制天数必须在0-365之间", http.StatusBadRequest) return } } if req.AutoTransferMinSpace != nil { - utils.Info("验证AutoTransferMinSpace: %d", *req.AutoTransferMinSpace) if *req.AutoTransferMinSpace < 100 || *req.AutoTransferMinSpace > 1024 { + utils.Warn("配置验证失败 - AutoTransferMinSpace超出范围: %d", *req.AutoTransferMinSpace) ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest) return } @@ -190,19 +189,17 @@ func UpdateSystemConfig(c *gin.Context) { // 验证公告相关字段 if req.Announcements != nil { - utils.Info("验证Announcements: '%s'", *req.Announcements) - // 可以在这里添加更详细的验证逻辑 + // 简化验证,仅在需要时添加逻辑 } // 转换为实体 configs := converter.RequestToSystemConfig(&req) if configs == nil { + utils.Error("配置数据转换失败") ErrorResponse(c, "数据转换失败", http.StatusInternalServerError) return } - utils.Info("准备更新配置,配置项数量: %d", len(configs)) - // 保存配置 err = repoManager.SystemConfigRepository.UpsertConfigs(configs) if err != nil { @@ -211,7 +208,7 @@ func UpdateSystemConfig(c *gin.Context) { return } - utils.Info("配置保存成功") + utils.Info("系统配置更新成功 - 更新项数: %d", len(configs)) // 安全刷新系统配置缓存 if err := repoManager.SystemConfigRepository.SafeRefreshConfigCache(); err != nil { diff --git a/main.go b/main.go index 0dc7fd2..91461ee 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,9 @@ import ( "fmt" "log" "os" + "os/signal" "strings" + "syscall" "time" "github.com/ctwj/urldb/config" @@ -38,7 +40,7 @@ func main() { } // 初始化日志系统 - if err := utils.InitLogger(nil); err != nil { + if err := utils.InitLogger(); err != nil { log.Fatal("初始化日志系统失败:", err) } @@ -84,6 +86,8 @@ func main() { utils.Fatal("数据库连接失败: %v", err) } + // 日志系统已简化,无需额外初始化 + // 创建Repository管理器 repoManager := repo.NewRepositoryManager(db.DB) @@ -463,6 +467,21 @@ func main() { port = "8080" } - utils.Info("服务器启动在端口 %s", port) - r.Run(":" + port) + // 设置优雅关闭 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + // 在goroutine中启动服务器 + go func() { + utils.Info("服务器启动在端口 %s", port) + if err := r.Run(":" + port); err != nil && err.Error() != "http: Server closed" { + utils.Fatal("服务器启动失败: %v", err) + } + }() + + // 等待信号 + <-quit + utils.Info("收到关闭信号,开始优雅关闭...") + + utils.Info("服务器已优雅关闭") } diff --git a/middleware/logging.go b/middleware/logging.go index d090d37..c8029c4 100644 --- a/middleware/logging.go +++ b/middleware/logging.go @@ -4,6 +4,7 @@ import ( "bytes" "io" "net/http" + "strings" "time" "github.com/ctwj/urldb/utils" @@ -55,41 +56,64 @@ func LoggingMiddleware(next http.Handler) http.Handler { }) } -// logRequest 记录请求日志 +// logRequest 记录请求日志 - 优化后仅记录异常和关键请求 func logRequest(r *http.Request, rw *responseWriter, duration time.Duration, requestBody []byte) { // 获取客户端IP clientIP := getClientIP(r) - // 获取用户代理 - userAgent := r.UserAgent() - if userAgent == "" { - userAgent = "Unknown" + // 判断是否需要记录日志的条件 + shouldLog := rw.statusCode >= 400 || // 错误状态码 + duration > 5*time.Second || // 耗时过长 + shouldLogPath(r.URL.Path) || // 关键路径 + isAdminPath(r.URL.Path) // 管理员路径 + + if !shouldLog { + return // 正常请求不记录日志,减少日志噪音 } - // 记录请求信息 - utils.Info("HTTP请求 - %s %s - IP: %s - User-Agent: %s - 状态码: %d - 耗时: %v", - r.Method, r.URL.Path, clientIP, userAgent, rw.statusCode, duration) - - // 如果是错误状态码,记录详细信息 + // 简化的日志格式,移除User-Agent以减少噪音 if rw.statusCode >= 400 { - utils.Error("HTTP错误 - %s %s - 状态码: %d - 响应体: %s", - r.Method, r.URL.Path, rw.statusCode, rw.body.String()) + // 错误请求记录详细信息 + utils.Error("HTTP异常 - %s %s - IP: %s - 状态码: %d - 耗时: %v", + r.Method, r.URL.Path, clientIP, rw.statusCode, duration) + + // 仅在错误状态下记录简要的请求信息 + if len(requestBody) > 0 && len(requestBody) <= 500 { + utils.Error("请求详情: %s", string(requestBody)) + } + } else if duration > 5*time.Second { + // 慢请求警告 + utils.Warn("HTTP慢请求 - %s %s - IP: %s - 耗时: %v", + r.Method, r.URL.Path, clientIP, duration) + } else { + // 关键路径的正常请求 + utils.Info("HTTP关键请求 - %s %s - IP: %s - 状态码: %d - 耗时: %v", + r.Method, r.URL.Path, clientIP, rw.statusCode, duration) + } +} + +// shouldLogPath 判断路径是否需要记录日志 +func shouldLogPath(path string) bool { + // 定义需要记录日志的关键路径 + keyPaths := []string{ + "/api/public/resources", + "/api/admin/config", + "/api/admin/users", + "/telegram/webhook", } - // 记录请求参数(仅对POST/PUT请求) - if (r.Method == "POST" || r.Method == "PUT") && len(requestBody) > 0 { - // 限制日志长度,避免日志文件过大 - if len(requestBody) > 1000 { - utils.Debug("请求体(截断): %s...", string(requestBody[:1000])) - } else { - utils.Debug("请求体: %s", string(requestBody)) + for _, keyPath := range keyPaths { + if strings.HasPrefix(path, keyPath) { + return true } } + return false +} - // 记录查询参数 - if len(r.URL.RawQuery) > 0 { - utils.Debug("查询参数: %s", r.URL.RawQuery) - } +// isAdminPath 判断是否为管理员路径 +func isAdminPath(path string) bool { + return strings.HasPrefix(path, "/api/admin/") || + strings.HasPrefix(path, "/admin/") } // getClientIP 获取客户端真实IP地址 diff --git a/services/telegram_bot_service.go b/services/telegram_bot_service.go index 132131c..4f46b5a 100644 --- a/services/telegram_bot_service.go +++ b/services/telegram_bot_service.go @@ -111,57 +111,55 @@ func (s *TelegramBotServiceImpl) loadConfig() error { s.config.ProxyUsername = "" s.config.ProxyPassword = "" + // 统计配置项数量,用于汇总日志 + configCount := 0 + for _, config := range configs { switch config.Key { case entity.ConfigKeyTelegramBotEnabled: s.config.Enabled = config.Value == "true" - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (Enabled: %v)", config.Key, config.Value, s.config.Enabled) case entity.ConfigKeyTelegramBotApiKey: s.config.ApiKey = config.Value - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = [HIDDEN]", config.Key) case entity.ConfigKeyTelegramAutoReplyEnabled: s.config.AutoReplyEnabled = config.Value == "true" - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (AutoReplyEnabled: %v)", config.Key, config.Value, s.config.AutoReplyEnabled) case entity.ConfigKeyTelegramAutoReplyTemplate: if config.Value != "" { s.config.AutoReplyTemplate = config.Value } - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, config.Value) case entity.ConfigKeyTelegramAutoDeleteEnabled: s.config.AutoDeleteEnabled = config.Value == "true" - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (AutoDeleteEnabled: %v)", config.Key, config.Value, s.config.AutoDeleteEnabled) case entity.ConfigKeyTelegramAutoDeleteInterval: if config.Value != "" { fmt.Sscanf(config.Value, "%d", &s.config.AutoDeleteInterval) } - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (AutoDeleteInterval: %d)", config.Key, config.Value, s.config.AutoDeleteInterval) case entity.ConfigKeyTelegramProxyEnabled: s.config.ProxyEnabled = config.Value == "true" - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyEnabled: %v)", config.Key, config.Value, s.config.ProxyEnabled) case entity.ConfigKeyTelegramProxyType: s.config.ProxyType = config.Value - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyType: %s)", config.Key, config.Value, s.config.ProxyType) case entity.ConfigKeyTelegramProxyHost: s.config.ProxyHost = config.Value - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]") case entity.ConfigKeyTelegramProxyPort: if config.Value != "" { fmt.Sscanf(config.Value, "%d", &s.config.ProxyPort) } - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyPort: %d)", config.Key, config.Value, s.config.ProxyPort) case entity.ConfigKeyTelegramProxyUsername: s.config.ProxyUsername = config.Value - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]") case entity.ConfigKeyTelegramProxyPassword: s.config.ProxyPassword = config.Value - utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]") default: - utils.Debug("未知配置: %s = %s", config.Key, config.Value) + utils.Debug("未知Telegram配置: %s", config.Key) } + configCount++ } - utils.Info("[TELEGRAM:SERVICE] Telegram Bot 配置加载完成: Enabled=%v, AutoReplyEnabled=%v, ApiKey长度=%d", - s.config.Enabled, s.config.AutoReplyEnabled, len(s.config.ApiKey)) + // 汇总输出配置加载结果,避免逐项日志 + proxyStatus := "禁用" + if s.config.ProxyEnabled { + proxyStatus = "启用" + } + + utils.TelegramInfo("配置加载完成 - Bot启用: %v, 自动回复: %v, 代理: %s, 配置项数: %d", + s.config.Enabled, s.config.AutoReplyEnabled, proxyStatus, configCount) return nil } diff --git a/task/task_processor.go b/task/task_processor.go index 8d8cfa3..02ac799 100644 --- a/task/task_processor.go +++ b/task/task_processor.go @@ -189,12 +189,22 @@ func (tm *TaskManager) StopTask(taskID uint) error { // processTask 处理任务 func (tm *TaskManager) processTask(ctx context.Context, task *entity.Task, processor TaskProcessor) { startTime := utils.GetCurrentTime() + + // 记录任务开始 + utils.Info("任务开始 - ID: %d, 类型: %s", task.ID, task.Type) + defer func() { tm.mu.Lock() delete(tm.running, task.ID) tm.mu.Unlock() + elapsedTime := time.Since(startTime) - utils.Info("processTask: 任务 %d 处理完成,耗时: %v,清理资源", task.ID, elapsedTime) + // 使用业务事件记录任务完成,只有异常情况才输出详细日志 + if elapsedTime > 30*time.Second { + utils.Warn("任务处理耗时较长 - ID: %d, 类型: %s, 耗时: %v", task.ID, task.Type, elapsedTime) + } + + utils.Info("任务完成 - ID: %d, 类型: %s, 耗时: %v", task.ID, task.Type, elapsedTime) }() utils.InfoWithFields(map[string]interface{}{ diff --git a/utils/logger.go b/utils/logger.go index aebc46e..c548003 100644 --- a/utils/logger.go +++ b/utils/logger.go @@ -1,7 +1,6 @@ package utils import ( - "encoding/json" "fmt" "io" "log" @@ -10,7 +9,6 @@ import ( "runtime" "strings" "sync" - "time" ) // LogLevel 日志级别 @@ -24,7 +22,7 @@ const ( FATAL ) -// String 返回日志级别的字符串表示 +// String 返回级别的字符串表示 func (l LogLevel) String() string { switch l { case DEBUG: @@ -42,280 +40,76 @@ func (l LogLevel) String() string { } } -// StructuredLogEntry 结构化日志条目 -type StructuredLogEntry struct { - Timestamp time.Time `json:"timestamp"` - Level string `json:"level"` - Message string `json:"message"` - Caller string `json:"caller"` - Module string `json:"module"` - Fields map[string]interface{} `json:"fields,omitempty"` -} - -// Logger 统一日志器 +// Logger 简化的日志器 type Logger struct { - debugLogger *log.Logger - infoLogger *log.Logger - warnLogger *log.Logger - errorLogger *log.Logger - fatalLogger *log.Logger - + level LogLevel + logger *log.Logger file *os.File - mu sync.Mutex - config *LogConfig -} - -// LogConfig 日志配置 -type LogConfig struct { - LogDir string // 日志目录 - LogLevel LogLevel // 日志级别 - MaxFileSize int64 // 单个日志文件最大大小(MB) - MaxBackups int // 最大备份文件数 - MaxAge int // 日志文件最大保留天数 - EnableConsole bool // 是否启用控制台输出 - EnableFile bool // 是否启用文件输出 - EnableRotation bool // 是否启用日志轮转 - StructuredLog bool // 是否启用结构化日志格式 -} - -// DefaultConfig 默认配置 -func DefaultConfig() *LogConfig { - // 从环境变量获取日志级别,默认为INFO - logLevel := getLogLevelFromEnv() - - return &LogConfig{ - LogDir: "logs", - LogLevel: logLevel, - MaxFileSize: 100, // 100MB - MaxBackups: 5, - MaxAge: 30, // 30天 - EnableConsole: true, - EnableFile: true, - EnableRotation: true, - StructuredLog: os.Getenv("STRUCTURED_LOG") == "true", // 从环境变量控制结构化日志 - } -} - -// getLogLevelFromEnv 从环境变量获取日志级别 -func getLogLevelFromEnv() LogLevel { - envLogLevel := os.Getenv("LOG_LEVEL") - envDebug := os.Getenv("DEBUG") - - // 如果设置了DEBUG环境变量为true,则使用DEBUG级别 - if envDebug == "true" || envDebug == "1" { - return DEBUG - } - - // 根据LOG_LEVEL环境变量设置日志级别 - switch strings.ToUpper(envLogLevel) { - case "DEBUG": - return DEBUG - case "INFO": - return INFO - case "WARN", "WARNING": - return WARN - case "ERROR": - return ERROR - case "FATAL": - return FATAL - default: - // 根据运行环境设置默认级别:开发环境DEBUG,生产环境INFO - if isDevelopment() { - return DEBUG - } - return INFO - } -} - -// isDevelopment 判断是否为开发环境 -func isDevelopment() bool { - env := os.Getenv("GO_ENV") - return env == "development" || env == "dev" || env == "local" || env == "test" -} - -// getEnvironment 获取当前环境类型 -func (l *Logger) getEnvironment() string { - if isDevelopment() { - return "development" - } - return "production" + mu sync.RWMutex } var ( globalLogger *Logger - onceLogger sync.Once + loggerOnce sync.Once ) -// InitLogger 初始化全局日志器 -func InitLogger(config *LogConfig) error { +// InitLogger 初始化日志器 +func InitLogger() error { var err error - onceLogger.Do(func() { - if config == nil { - config = DefaultConfig() + loggerOnce.Do(func() { + globalLogger = &Logger{ + level: INFO, + logger: log.New(os.Stdout, "", log.LstdFlags), } - globalLogger, err = NewLogger(config) + // 创建日志目录 + logDir := "logs" + if err = os.MkdirAll(logDir, 0755); err != nil { + return + } + + // 创建日志文件 + logFile := filepath.Join(logDir, "app.log") + globalLogger.file, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return + } + + // 同时输出到控制台和文件 + globalLogger.logger = log.New(io.MultiWriter(os.Stdout, globalLogger.file), "", log.LstdFlags) }) + return err } // GetLogger 获取全局日志器 func GetLogger() *Logger { if globalLogger == nil { - InitLogger(nil) + InitLogger() } return globalLogger } -// NewLogger 创建新的日志器 -func NewLogger(config *LogConfig) (*Logger, error) { - if config == nil { - config = DefaultConfig() - } - - logger := &Logger{ - config: config, - } - - // 创建日志目录 - if config.EnableFile { - if err := os.MkdirAll(config.LogDir, 0755); err != nil { - return nil, fmt.Errorf("创建日志目录失败: %v", err) - } - } - - // 初始化日志文件 - if err := logger.initLogFile(); err != nil { - return nil, err - } - - // 初始化日志器 - logger.initLoggers() - - // 启动日志轮转检查 - if config.EnableRotation { - go logger.startRotationCheck() - } - - // 打印日志配置信息 - logger.Info("日志系统初始化完成 - 级别: %s, 环境: %s", - config.LogLevel.String(), - logger.getEnvironment()) - - return logger, nil -} - -// initLogFile 初始化日志文件 -func (l *Logger) initLogFile() error { - if !l.config.EnableFile { - return nil - } - - l.mu.Lock() - defer l.mu.Unlock() - - // 关闭现有文件 - if l.file != nil { - l.file.Close() - } - - // 创建新的日志文件 - logFile := filepath.Join(l.config.LogDir, fmt.Sprintf("app_%s.log", GetCurrentTime().Format("2006-01-02"))) - file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - if err != nil { - return fmt.Errorf("创建日志文件失败: %v", err) - } - - l.file = file - return nil -} - -// initLoggers 初始化各个级别的日志器 -func (l *Logger) initLoggers() { - var writers []io.Writer - - // 添加控制台输出 - if l.config.EnableConsole { - writers = append(writers, os.Stdout) - } - - // 添加文件输出 - if l.config.EnableFile && l.file != nil { - writers = append(writers, l.file) - } - - multiWriter := io.MultiWriter(writers...) - - // 创建各个级别的日志器 - l.debugLogger = log.New(multiWriter, "[DEBUG] ", log.LstdFlags) - l.infoLogger = log.New(multiWriter, "[INFO] ", log.LstdFlags) - l.warnLogger = log.New(multiWriter, "[WARN] ", log.LstdFlags) - l.errorLogger = log.New(multiWriter, "[ERROR] ", log.LstdFlags) - l.fatalLogger = log.New(multiWriter, "[FATAL] ", log.LstdFlags) -} - // log 内部日志方法 func (l *Logger) log(level LogLevel, format string, args ...interface{}) { - if level < l.config.LogLevel { + if level < l.level { return } // 获取调用者信息 _, file, line, ok := runtime.Caller(2) - if !ok { - file = "unknown" - line = 0 + caller := "unknown" + if ok { + caller = fmt.Sprintf("%s:%d", filepath.Base(file), line) } - // 提取文件名作为模块名 - fileName := filepath.Base(file) - moduleName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) - - // 格式化消息 message := fmt.Sprintf(format, args...) + logMessage := fmt.Sprintf("[%s] [%s] %s", level.String(), caller, message) - // 添加调用位置信息 - caller := fmt.Sprintf("%s:%d", fileName, line) + l.logger.Println(logMessage) - if l.config.StructuredLog { - // 结构化日志格式 - entry := StructuredLogEntry{ - Timestamp: GetCurrentTime(), - Level: level.String(), - Message: message, - Caller: caller, - Module: moduleName, - } - - jsonBytes, err := json.Marshal(entry) - if err != nil { - // 如果JSON序列化失败,回退到普通格式 - fullMessage := fmt.Sprintf("[%s] [%s:%d] %s", level.String(), fileName, line, message) - l.logToLevel(level, fullMessage) - return - } - - l.logToLevel(level, string(jsonBytes)) - } else { - // 普通文本格式 - fullMessage := fmt.Sprintf("[%s] [%s:%d] %s", level.String(), fileName, line, message) - l.logToLevel(level, fullMessage) - } -} - -// logToLevel 根据级别输出日志 -func (l *Logger) logToLevel(level LogLevel, message string) { - switch level { - case DEBUG: - l.debugLogger.Println(message) - case INFO: - l.infoLogger.Println(message) - case WARN: - l.warnLogger.Println(message) - case ERROR: - l.errorLogger.Println(message) - case FATAL: - l.fatalLogger.Println(message) + // Fatal级别终止程序 + if level == FATAL { os.Exit(1) } } @@ -345,162 +139,72 @@ func (l *Logger) Fatal(format string, args ...interface{}) { l.log(FATAL, format, args...) } -// startRotationCheck 启动日志轮转检查 -func (l *Logger) startRotationCheck() { - ticker := time.NewTicker(1 * time.Hour) // 每小时检查一次 - defer ticker.Stop() - - for range ticker.C { - l.checkRotation() - } +// TelegramDebug Telegram调试日志 +func (l *Logger) TelegramDebug(format string, args ...interface{}) { + l.log(DEBUG, "[TELEGRAM] "+format, args...) } -// checkRotation 检查是否需要轮转日志 -func (l *Logger) checkRotation() { - if !l.config.EnableFile || l.file == nil { - return - } - - // 检查文件大小 - fileInfo, err := l.file.Stat() - if err != nil { - return - } - - // 如果文件超过最大大小,进行轮转 - if fileInfo.Size() > l.config.MaxFileSize*1024*1024 { - l.rotateLog() - } - - // 清理旧日志文件 - l.cleanOldLogs() +// TelegramInfo Telegram信息日志 +func (l *Logger) TelegramInfo(format string, args ...interface{}) { + l.log(INFO, "[TELEGRAM] "+format, args...) } -// rotateLog 轮转日志文件 -func (l *Logger) rotateLog() { +// TelegramWarn Telegram警告日志 +func (l *Logger) TelegramWarn(format string, args ...interface{}) { + l.log(WARN, "[TELEGRAM] "+format, args...) +} + +// TelegramError Telegram错误日志 +func (l *Logger) TelegramError(format string, args ...interface{}) { + l.log(ERROR, "[TELEGRAM] "+format, args...) +} + +// DebugWithFields 带字段的调试日志 +func (l *Logger) DebugWithFields(fields map[string]interface{}, format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + if len(fields) > 0 { + var fieldStrs []string + for k, v := range fields { + fieldStrs = append(fieldStrs, fmt.Sprintf("%s=%v", k, v)) + } + message = fmt.Sprintf("%s [%s]", message, strings.Join(fieldStrs, ", ")) + } + l.log(DEBUG, message) +} + +// InfoWithFields 带字段的信息日志 +func (l *Logger) InfoWithFields(fields map[string]interface{}, format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + if len(fields) > 0 { + var fieldStrs []string + for k, v := range fields { + fieldStrs = append(fieldStrs, fmt.Sprintf("%s=%v", k, v)) + } + message = fmt.Sprintf("%s [%s]", message, strings.Join(fieldStrs, ", ")) + } + l.log(INFO, message) +} + +// ErrorWithFields 带字段的错误日志 +func (l *Logger) ErrorWithFields(fields map[string]interface{}, format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + if len(fields) > 0 { + var fieldStrs []string + for k, v := range fields { + fieldStrs = append(fieldStrs, fmt.Sprintf("%s=%v", k, v)) + } + message = fmt.Sprintf("%s [%s]", message, strings.Join(fieldStrs, ", ")) + } + l.log(ERROR, message) +} + +// Close 关闭日志文件 +func (l *Logger) Close() { l.mu.Lock() defer l.mu.Unlock() - - // 关闭当前文件 if l.file != nil { l.file.Close() } - - // 重命名当前日志文件 - currentLogFile := filepath.Join(l.config.LogDir, fmt.Sprintf("app_%s.log", GetCurrentTime().Format("2006-01-02"))) - backupLogFile := filepath.Join(l.config.LogDir, fmt.Sprintf("app_%s_%s.log", GetCurrentTime().Format("2006-01-02"), GetCurrentTime().Format("15-04-05"))) - - if _, err := os.Stat(currentLogFile); err == nil { - os.Rename(currentLogFile, backupLogFile) - } - - // 创建新的日志文件 - l.initLogFile() - l.initLoggers() -} - -// cleanOldLogs 清理旧日志文件 -func (l *Logger) cleanOldLogs() { - if l.config.MaxAge <= 0 { - return - } - - files, err := filepath.Glob(filepath.Join(l.config.LogDir, "app_*.log")) - if err != nil { - return - } - - cutoffTime := GetCurrentTime().AddDate(0, 0, -l.config.MaxAge) - - for _, file := range files { - fileInfo, err := os.Stat(file) - if err != nil { - continue - } - - if fileInfo.ModTime().Before(cutoffTime) { - os.Remove(file) - } - } -} - -// Min 返回两个整数中的较小值 -func Min(a, b int) int { - if a < b { - return a - } - return b -} - -// 结构化日志方法 -func (l *Logger) DebugWithFields(fields map[string]interface{}, format string, args ...interface{}) { - l.logWithFields(DEBUG, fields, format, args...) -} - -func (l *Logger) InfoWithFields(fields map[string]interface{}, format string, args ...interface{}) { - l.logWithFields(INFO, fields, format, args...) -} - -func (l *Logger) WarnWithFields(fields map[string]interface{}, format string, args ...interface{}) { - l.logWithFields(WARN, fields, format, args...) -} - -func (l *Logger) ErrorWithFields(fields map[string]interface{}, format string, args ...interface{}) { - l.logWithFields(ERROR, fields, format, args...) -} - -func (l *Logger) FatalWithFields(fields map[string]interface{}, format string, args ...interface{}) { - l.logWithFields(FATAL, fields, format, args...) -} - -// logWithFields 带字段的结构化日志方法 -func (l *Logger) logWithFields(level LogLevel, fields map[string]interface{}, format string, args ...interface{}) { - if level < l.config.LogLevel { - return - } - - // 获取调用者信息 - _, file, line, ok := runtime.Caller(2) - if !ok { - file = "unknown" - line = 0 - } - - // 提取文件名作为模块名 - fileName := filepath.Base(file) - moduleName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) - - // 格式化消息 - message := fmt.Sprintf(format, args...) - - // 添加调用位置信息 - caller := fmt.Sprintf("%s:%d", fileName, line) - - if l.config.StructuredLog { - // 结构化日志格式 - entry := StructuredLogEntry{ - Timestamp: GetCurrentTime(), - Level: level.String(), - Message: message, - Caller: caller, - Module: moduleName, - Fields: fields, - } - - jsonBytes, err := json.Marshal(entry) - if err != nil { - // 如果JSON序列化失败,回退到普通格式 - fullMessage := fmt.Sprintf("[%s] [%s:%d] %s - Fields: %v", level.String(), fileName, line, message, fields) - l.logToLevel(level, fullMessage) - return - } - - l.logToLevel(level, string(jsonBytes)) - } else { - // 普通文本格式 - fullMessage := fmt.Sprintf("[%s] [%s:%d] %s - Fields: %v", level.String(), fileName, line, message, fields) - l.logToLevel(level, fullMessage) - } } // 全局便捷函数 @@ -524,7 +228,22 @@ func Fatal(format string, args ...interface{}) { GetLogger().Fatal(format, args...) } -// 全局结构化日志便捷函数 +func TelegramDebug(format string, args ...interface{}) { + GetLogger().TelegramDebug(format, args...) +} + +func TelegramInfo(format string, args ...interface{}) { + GetLogger().TelegramInfo(format, args...) +} + +func TelegramWarn(format string, args ...interface{}) { + GetLogger().TelegramWarn(format, args...) +} + +func TelegramError(format string, args ...interface{}) { + GetLogger().TelegramError(format, args...) +} + func DebugWithFields(fields map[string]interface{}, format string, args ...interface{}) { GetLogger().DebugWithFields(fields, format, args...) } @@ -533,14 +252,15 @@ func InfoWithFields(fields map[string]interface{}, format string, args ...interf GetLogger().InfoWithFields(fields, format, args...) } -func WarnWithFields(fields map[string]interface{}, format string, args ...interface{}) { - GetLogger().WarnWithFields(fields, format, args...) -} - func ErrorWithFields(fields map[string]interface{}, format string, args ...interface{}) { GetLogger().ErrorWithFields(fields, format, args...) } -func FatalWithFields(fields map[string]interface{}, format string, args ...interface{}) { - GetLogger().FatalWithFields(fields, format, args...) +// Min 返回两个整数中的较小值 +func Min(a, b int) int { + if a < b { + return a + } + return b } +