mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: 日志优化
This commit is contained in:
@@ -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_resource_id ON resource_tags(resource_id)")
|
||||||
db.Exec("CREATE INDEX IF NOT EXISTS idx_resource_tags_tag_id ON resource_tags(tag_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 只在数据库为空时插入默认数据
|
// insertDefaultDataIfEmpty 只在数据库为空时插入默认数据
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -91,30 +92,9 @@ func (h *PublicAPIHandler) logAPIAccess(c *gin.Context, startTime time.Time, pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步记录日志,避免影响API响应时间
|
// 记录API访问日志 - 使用简单日志记录
|
||||||
go func() {
|
h.recordAPIAccessToDB(ip, userAgent, endpoint, method, requestParams,
|
||||||
defer func() {
|
c.Writer.Status(), responseData, processCount, errorMessage, processingTime)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBatchResources godoc
|
// AddBatchResources godoc
|
||||||
@@ -466,3 +446,49 @@ func (h *PublicAPIHandler) GetHotDramas(c *gin.Context) {
|
|||||||
h.logAPIAccess(c, startTime, len(hotDramaResponses), responseData, "")
|
h.logAPIAccess(c, startTime, len(hotDramaResponses), responseData, "")
|
||||||
SuccessResponse(c, 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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|||||||
@@ -145,27 +145,26 @@ func UpdateSystemConfig(c *gin.Context) {
|
|||||||
utils.Info("当前配置数量: %d", len(currentConfigs))
|
utils.Info("当前配置数量: %d", len(currentConfigs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证参数 - 只验证提交的字段
|
// 验证参数 - 只验证提交的字段,仅在验证失败时记录日志
|
||||||
utils.Info("开始验证参数")
|
|
||||||
if req.SiteTitle != nil {
|
if req.SiteTitle != nil {
|
||||||
utils.Info("验证SiteTitle: '%s', 长度: %d", *req.SiteTitle, len(*req.SiteTitle))
|
|
||||||
if len(*req.SiteTitle) < 1 || len(*req.SiteTitle) > 100 {
|
if len(*req.SiteTitle) < 1 || len(*req.SiteTitle) > 100 {
|
||||||
|
utils.Warn("配置验证失败 - SiteTitle长度无效: %d", len(*req.SiteTitle))
|
||||||
ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest)
|
ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.AutoProcessInterval != nil {
|
if req.AutoProcessInterval != nil {
|
||||||
utils.Info("验证AutoProcessInterval: %d", *req.AutoProcessInterval)
|
|
||||||
if *req.AutoProcessInterval < 1 || *req.AutoProcessInterval > 1440 {
|
if *req.AutoProcessInterval < 1 || *req.AutoProcessInterval > 1440 {
|
||||||
|
utils.Warn("配置验证失败 - AutoProcessInterval超出范围: %d", *req.AutoProcessInterval)
|
||||||
ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest)
|
ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.PageSize != nil {
|
if req.PageSize != nil {
|
||||||
utils.Info("验证PageSize: %d", *req.PageSize)
|
|
||||||
if *req.PageSize < 10 || *req.PageSize > 500 {
|
if *req.PageSize < 10 || *req.PageSize > 500 {
|
||||||
|
utils.Warn("配置验证失败 - PageSize超出范围: %d", *req.PageSize)
|
||||||
ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest)
|
ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -173,16 +172,16 @@ func UpdateSystemConfig(c *gin.Context) {
|
|||||||
|
|
||||||
// 验证自动转存配置
|
// 验证自动转存配置
|
||||||
if req.AutoTransferLimitDays != nil {
|
if req.AutoTransferLimitDays != nil {
|
||||||
utils.Info("验证AutoTransferLimitDays: %d", *req.AutoTransferLimitDays)
|
|
||||||
if *req.AutoTransferLimitDays < 0 || *req.AutoTransferLimitDays > 365 {
|
if *req.AutoTransferLimitDays < 0 || *req.AutoTransferLimitDays > 365 {
|
||||||
|
utils.Warn("配置验证失败 - AutoTransferLimitDays超出范围: %d", *req.AutoTransferLimitDays)
|
||||||
ErrorResponse(c, "自动转存限制天数必须在0-365之间", http.StatusBadRequest)
|
ErrorResponse(c, "自动转存限制天数必须在0-365之间", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.AutoTransferMinSpace != nil {
|
if req.AutoTransferMinSpace != nil {
|
||||||
utils.Info("验证AutoTransferMinSpace: %d", *req.AutoTransferMinSpace)
|
|
||||||
if *req.AutoTransferMinSpace < 100 || *req.AutoTransferMinSpace > 1024 {
|
if *req.AutoTransferMinSpace < 100 || *req.AutoTransferMinSpace > 1024 {
|
||||||
|
utils.Warn("配置验证失败 - AutoTransferMinSpace超出范围: %d", *req.AutoTransferMinSpace)
|
||||||
ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest)
|
ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -190,19 +189,17 @@ func UpdateSystemConfig(c *gin.Context) {
|
|||||||
|
|
||||||
// 验证公告相关字段
|
// 验证公告相关字段
|
||||||
if req.Announcements != nil {
|
if req.Announcements != nil {
|
||||||
utils.Info("验证Announcements: '%s'", *req.Announcements)
|
// 简化验证,仅在需要时添加逻辑
|
||||||
// 可以在这里添加更详细的验证逻辑
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为实体
|
// 转换为实体
|
||||||
configs := converter.RequestToSystemConfig(&req)
|
configs := converter.RequestToSystemConfig(&req)
|
||||||
if configs == nil {
|
if configs == nil {
|
||||||
|
utils.Error("配置数据转换失败")
|
||||||
ErrorResponse(c, "数据转换失败", http.StatusInternalServerError)
|
ErrorResponse(c, "数据转换失败", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Info("准备更新配置,配置项数量: %d", len(configs))
|
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
err = repoManager.SystemConfigRepository.UpsertConfigs(configs)
|
err = repoManager.SystemConfigRepository.UpsertConfigs(configs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -211,7 +208,7 @@ func UpdateSystemConfig(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Info("配置保存成功")
|
utils.Info("系统配置更新成功 - 更新项数: %d", len(configs))
|
||||||
|
|
||||||
// 安全刷新系统配置缓存
|
// 安全刷新系统配置缓存
|
||||||
if err := repoManager.SystemConfigRepository.SafeRefreshConfigCache(); err != nil {
|
if err := repoManager.SystemConfigRepository.SafeRefreshConfigCache(); err != nil {
|
||||||
|
|||||||
25
main.go
25
main.go
@@ -4,7 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ctwj/urldb/config"
|
"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)
|
log.Fatal("初始化日志系统失败:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,8 @@ func main() {
|
|||||||
utils.Fatal("数据库连接失败: %v", err)
|
utils.Fatal("数据库连接失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 日志系统已简化,无需额外初始化
|
||||||
|
|
||||||
// 创建Repository管理器
|
// 创建Repository管理器
|
||||||
repoManager := repo.NewRepositoryManager(db.DB)
|
repoManager := repo.NewRepositoryManager(db.DB)
|
||||||
|
|
||||||
@@ -463,6 +467,21 @@ func main() {
|
|||||||
port = "8080"
|
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("服务器已优雅关闭")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ctwj/urldb/utils"
|
"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) {
|
func logRequest(r *http.Request, rw *responseWriter, duration time.Duration, requestBody []byte) {
|
||||||
// 获取客户端IP
|
// 获取客户端IP
|
||||||
clientIP := getClientIP(r)
|
clientIP := getClientIP(r)
|
||||||
|
|
||||||
// 获取用户代理
|
// 判断是否需要记录日志的条件
|
||||||
userAgent := r.UserAgent()
|
shouldLog := rw.statusCode >= 400 || // 错误状态码
|
||||||
if userAgent == "" {
|
duration > 5*time.Second || // 耗时过长
|
||||||
userAgent = "Unknown"
|
shouldLogPath(r.URL.Path) || // 关键路径
|
||||||
|
isAdminPath(r.URL.Path) // 管理员路径
|
||||||
|
|
||||||
|
if !shouldLog {
|
||||||
|
return // 正常请求不记录日志,减少日志噪音
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录请求信息
|
// 简化的日志格式,移除User-Agent以减少噪音
|
||||||
utils.Info("HTTP请求 - %s %s - IP: %s - User-Agent: %s - 状态码: %d - 耗时: %v",
|
|
||||||
r.Method, r.URL.Path, clientIP, userAgent, rw.statusCode, duration)
|
|
||||||
|
|
||||||
// 如果是错误状态码,记录详细信息
|
|
||||||
if rw.statusCode >= 400 {
|
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请求)
|
for _, keyPath := range keyPaths {
|
||||||
if (r.Method == "POST" || r.Method == "PUT") && len(requestBody) > 0 {
|
if strings.HasPrefix(path, keyPath) {
|
||||||
// 限制日志长度,避免日志文件过大
|
return true
|
||||||
if len(requestBody) > 1000 {
|
|
||||||
utils.Debug("请求体(截断): %s...", string(requestBody[:1000]))
|
|
||||||
} else {
|
|
||||||
utils.Debug("请求体: %s", string(requestBody))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 记录查询参数
|
// isAdminPath 判断是否为管理员路径
|
||||||
if len(r.URL.RawQuery) > 0 {
|
func isAdminPath(path string) bool {
|
||||||
utils.Debug("查询参数: %s", r.URL.RawQuery)
|
return strings.HasPrefix(path, "/api/admin/") ||
|
||||||
}
|
strings.HasPrefix(path, "/admin/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClientIP 获取客户端真实IP地址
|
// getClientIP 获取客户端真实IP地址
|
||||||
|
|||||||
@@ -111,57 +111,55 @@ func (s *TelegramBotServiceImpl) loadConfig() error {
|
|||||||
s.config.ProxyUsername = ""
|
s.config.ProxyUsername = ""
|
||||||
s.config.ProxyPassword = ""
|
s.config.ProxyPassword = ""
|
||||||
|
|
||||||
|
// 统计配置项数量,用于汇总日志
|
||||||
|
configCount := 0
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
switch config.Key {
|
switch config.Key {
|
||||||
case entity.ConfigKeyTelegramBotEnabled:
|
case entity.ConfigKeyTelegramBotEnabled:
|
||||||
s.config.Enabled = config.Value == "true"
|
s.config.Enabled = config.Value == "true"
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (Enabled: %v)", config.Key, config.Value, s.config.Enabled)
|
|
||||||
case entity.ConfigKeyTelegramBotApiKey:
|
case entity.ConfigKeyTelegramBotApiKey:
|
||||||
s.config.ApiKey = config.Value
|
s.config.ApiKey = config.Value
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
|
||||||
case entity.ConfigKeyTelegramAutoReplyEnabled:
|
case entity.ConfigKeyTelegramAutoReplyEnabled:
|
||||||
s.config.AutoReplyEnabled = config.Value == "true"
|
s.config.AutoReplyEnabled = config.Value == "true"
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (AutoReplyEnabled: %v)", config.Key, config.Value, s.config.AutoReplyEnabled)
|
|
||||||
case entity.ConfigKeyTelegramAutoReplyTemplate:
|
case entity.ConfigKeyTelegramAutoReplyTemplate:
|
||||||
if config.Value != "" {
|
if config.Value != "" {
|
||||||
s.config.AutoReplyTemplate = config.Value
|
s.config.AutoReplyTemplate = config.Value
|
||||||
}
|
}
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, config.Value)
|
|
||||||
case entity.ConfigKeyTelegramAutoDeleteEnabled:
|
case entity.ConfigKeyTelegramAutoDeleteEnabled:
|
||||||
s.config.AutoDeleteEnabled = config.Value == "true"
|
s.config.AutoDeleteEnabled = config.Value == "true"
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (AutoDeleteEnabled: %v)", config.Key, config.Value, s.config.AutoDeleteEnabled)
|
|
||||||
case entity.ConfigKeyTelegramAutoDeleteInterval:
|
case entity.ConfigKeyTelegramAutoDeleteInterval:
|
||||||
if config.Value != "" {
|
if config.Value != "" {
|
||||||
fmt.Sscanf(config.Value, "%d", &s.config.AutoDeleteInterval)
|
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:
|
case entity.ConfigKeyTelegramProxyEnabled:
|
||||||
s.config.ProxyEnabled = config.Value == "true"
|
s.config.ProxyEnabled = config.Value == "true"
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyEnabled: %v)", config.Key, config.Value, s.config.ProxyEnabled)
|
|
||||||
case entity.ConfigKeyTelegramProxyType:
|
case entity.ConfigKeyTelegramProxyType:
|
||||||
s.config.ProxyType = config.Value
|
s.config.ProxyType = config.Value
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyType: %s)", config.Key, config.Value, s.config.ProxyType)
|
|
||||||
case entity.ConfigKeyTelegramProxyHost:
|
case entity.ConfigKeyTelegramProxyHost:
|
||||||
s.config.ProxyHost = config.Value
|
s.config.ProxyHost = config.Value
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]")
|
|
||||||
case entity.ConfigKeyTelegramProxyPort:
|
case entity.ConfigKeyTelegramProxyPort:
|
||||||
if config.Value != "" {
|
if config.Value != "" {
|
||||||
fmt.Sscanf(config.Value, "%d", &s.config.ProxyPort)
|
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:
|
case entity.ConfigKeyTelegramProxyUsername:
|
||||||
s.config.ProxyUsername = config.Value
|
s.config.ProxyUsername = config.Value
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]")
|
|
||||||
case entity.ConfigKeyTelegramProxyPassword:
|
case entity.ConfigKeyTelegramProxyPassword:
|
||||||
s.config.ProxyPassword = config.Value
|
s.config.ProxyPassword = config.Value
|
||||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]")
|
|
||||||
default:
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,12 +189,22 @@ func (tm *TaskManager) StopTask(taskID uint) error {
|
|||||||
// processTask 处理任务
|
// processTask 处理任务
|
||||||
func (tm *TaskManager) processTask(ctx context.Context, task *entity.Task, processor TaskProcessor) {
|
func (tm *TaskManager) processTask(ctx context.Context, task *entity.Task, processor TaskProcessor) {
|
||||||
startTime := utils.GetCurrentTime()
|
startTime := utils.GetCurrentTime()
|
||||||
|
|
||||||
|
// 记录任务开始
|
||||||
|
utils.Info("任务开始 - ID: %d, 类型: %s", task.ID, task.Type)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
tm.mu.Lock()
|
tm.mu.Lock()
|
||||||
delete(tm.running, task.ID)
|
delete(tm.running, task.ID)
|
||||||
tm.mu.Unlock()
|
tm.mu.Unlock()
|
||||||
|
|
||||||
elapsedTime := time.Since(startTime)
|
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{}{
|
utils.InfoWithFields(map[string]interface{}{
|
||||||
|
|||||||
514
utils/logger.go
514
utils/logger.go
@@ -1,7 +1,6 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -10,7 +9,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogLevel 日志级别
|
// LogLevel 日志级别
|
||||||
@@ -24,7 +22,7 @@ const (
|
|||||||
FATAL
|
FATAL
|
||||||
)
|
)
|
||||||
|
|
||||||
// String 返回日志级别的字符串表示
|
// String 返回级别的字符串表示
|
||||||
func (l LogLevel) String() string {
|
func (l LogLevel) String() string {
|
||||||
switch l {
|
switch l {
|
||||||
case DEBUG:
|
case DEBUG:
|
||||||
@@ -42,280 +40,76 @@ func (l LogLevel) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StructuredLogEntry 结构化日志条目
|
// Logger 简化的日志器
|
||||||
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 统一日志器
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
debugLogger *log.Logger
|
level LogLevel
|
||||||
infoLogger *log.Logger
|
logger *log.Logger
|
||||||
warnLogger *log.Logger
|
|
||||||
errorLogger *log.Logger
|
|
||||||
fatalLogger *log.Logger
|
|
||||||
|
|
||||||
file *os.File
|
file *os.File
|
||||||
mu sync.Mutex
|
mu sync.RWMutex
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalLogger *Logger
|
globalLogger *Logger
|
||||||
onceLogger sync.Once
|
loggerOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitLogger 初始化全局日志器
|
// InitLogger 初始化日志器
|
||||||
func InitLogger(config *LogConfig) error {
|
func InitLogger() error {
|
||||||
var err error
|
var err error
|
||||||
onceLogger.Do(func() {
|
loggerOnce.Do(func() {
|
||||||
if config == nil {
|
globalLogger = &Logger{
|
||||||
config = DefaultConfig()
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogger 获取全局日志器
|
// GetLogger 获取全局日志器
|
||||||
func GetLogger() *Logger {
|
func GetLogger() *Logger {
|
||||||
if globalLogger == nil {
|
if globalLogger == nil {
|
||||||
InitLogger(nil)
|
InitLogger()
|
||||||
}
|
}
|
||||||
return globalLogger
|
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 内部日志方法
|
// log 内部日志方法
|
||||||
func (l *Logger) log(level LogLevel, format string, args ...interface{}) {
|
func (l *Logger) log(level LogLevel, format string, args ...interface{}) {
|
||||||
if level < l.config.LogLevel {
|
if level < l.level {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取调用者信息
|
// 获取调用者信息
|
||||||
_, file, line, ok := runtime.Caller(2)
|
_, file, line, ok := runtime.Caller(2)
|
||||||
if !ok {
|
caller := "unknown"
|
||||||
file = "unknown"
|
if ok {
|
||||||
line = 0
|
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...)
|
message := fmt.Sprintf(format, args...)
|
||||||
|
logMessage := fmt.Sprintf("[%s] [%s] %s", level.String(), caller, message)
|
||||||
|
|
||||||
// 添加调用位置信息
|
l.logger.Println(logMessage)
|
||||||
caller := fmt.Sprintf("%s:%d", fileName, line)
|
|
||||||
|
|
||||||
if l.config.StructuredLog {
|
// Fatal级别终止程序
|
||||||
// 结构化日志格式
|
if level == FATAL {
|
||||||
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)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,162 +139,72 @@ func (l *Logger) Fatal(format string, args ...interface{}) {
|
|||||||
l.log(FATAL, format, args...)
|
l.log(FATAL, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startRotationCheck 启动日志轮转检查
|
// TelegramDebug Telegram调试日志
|
||||||
func (l *Logger) startRotationCheck() {
|
func (l *Logger) TelegramDebug(format string, args ...interface{}) {
|
||||||
ticker := time.NewTicker(1 * time.Hour) // 每小时检查一次
|
l.log(DEBUG, "[TELEGRAM] "+format, args...)
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for range ticker.C {
|
|
||||||
l.checkRotation()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRotation 检查是否需要轮转日志
|
// TelegramInfo Telegram信息日志
|
||||||
func (l *Logger) checkRotation() {
|
func (l *Logger) TelegramInfo(format string, args ...interface{}) {
|
||||||
if !l.config.EnableFile || l.file == nil {
|
l.log(INFO, "[TELEGRAM] "+format, args...)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查文件大小
|
|
||||||
fileInfo, err := l.file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果文件超过最大大小,进行轮转
|
|
||||||
if fileInfo.Size() > l.config.MaxFileSize*1024*1024 {
|
|
||||||
l.rotateLog()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理旧日志文件
|
|
||||||
l.cleanOldLogs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rotateLog 轮转日志文件
|
// TelegramWarn Telegram警告日志
|
||||||
func (l *Logger) rotateLog() {
|
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()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
// 关闭当前文件
|
|
||||||
if l.file != nil {
|
if l.file != nil {
|
||||||
l.file.Close()
|
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...)
|
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{}) {
|
func DebugWithFields(fields map[string]interface{}, format string, args ...interface{}) {
|
||||||
GetLogger().DebugWithFields(fields, format, args...)
|
GetLogger().DebugWithFields(fields, format, args...)
|
||||||
}
|
}
|
||||||
@@ -533,14 +252,15 @@ func InfoWithFields(fields map[string]interface{}, format string, args ...interf
|
|||||||
GetLogger().InfoWithFields(fields, format, args...)
|
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{}) {
|
func ErrorWithFields(fields map[string]interface{}, format string, args ...interface{}) {
|
||||||
GetLogger().ErrorWithFields(fields, format, args...)
|
GetLogger().ErrorWithFields(fields, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FatalWithFields(fields map[string]interface{}, format string, args ...interface{}) {
|
// Min 返回两个整数中的较小值
|
||||||
GetLogger().FatalWithFields(fields, format, args...)
|
func Min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user