Browse Source

refactor(log): 重构日志模块并优化日志记录功能- 新增按服务类型分割日志的功能,支持API和Task日志分开记录
- 增加按日期滚动日志文件的功能,提高日志管理的灵活性- 优化日志格式和编码,提高日志的可读性和性能
- 添加日志目录自动创建功能,确保日志记录的可靠性
- 更新日志配置参数,提供更多定制选项

fusu 3 months ago
parent
commit
f5e84789a7

+ 1 - 1
api/v1/GameShield.go

@@ -25,7 +25,7 @@ type GameShieldSubmitResponse struct {
 type GetGameShieldRuleIdRequest struct {
 	AppName string `json:"app_name" form:"app_name"`
 	Name    string `json:"name" form:"name"`
-	Id      int    `json:"id" form:"id"`
+	HostId  int    `json:"host_id" form:"host_id"`
 }
 
 type GetGameShieldRequiredResponse struct {

+ 3 - 1
cmd/server/main.go

@@ -31,7 +31,9 @@ func main() {
 	flag.Parse()
 	conf := config.NewConfig(*envConf)
 
-	logger := log.NewLog(conf)
+	// 检查是否有环境变量指定服务类型
+	serviceType := log.API
+	logger := log.NewServiceLog(conf, serviceType)
 
 	app, cleanup, err := wire.NewWire(conf, logger)
 	defer cleanup()

+ 3 - 1
cmd/task/main.go

@@ -13,7 +13,9 @@ func main() {
 	flag.Parse()
 	conf := config.NewConfig(*envConf)
 
-	logger := log.NewLog(conf)
+	// 使用Task服务类型初始化日志
+	serviceType := log.Task
+	logger := log.NewServiceLog(conf, serviceType)
 	logger.Info("start task")
 	app, cleanup, err := wire.NewWire(conf, logger)
 	defer cleanup()

+ 11 - 3
config/local.yml

@@ -30,11 +30,19 @@ data:
 log:
   log_level: debug
   encoding: console           # json or console
+  log_format: "2006-01-02"   # 按天生成日志文件
+  # 区分API和Task日志
+  api_log_file: "./storage/logs/api-%s.log" # %s会被替换为日期
+  task_log_file: "./storage/logs/task-%s.log" # %s会被替换为日期
+  # 兼容旧版本配置
   log_file_name: "./storage/logs/server.log"
   max_backups: 30
-  max_age: 7
-  max_size: 1024
-  compress: true
+  max_age: 30            # 保存30天日志
+  max_size: 200          # 单个文件最大200M
+  compress: true         # 自动压缩
+  # 日志过滤和美化
+  hide_sql_args: true    # 隐藏SQL参数详情
+  hide_request_body: false # 是否隐藏请求体内容
 
 crawler:
   username: "admin"

+ 11 - 3
config/prod.yml

@@ -30,8 +30,16 @@ data:
 log:
   log_level: info
   encoding: json           # json or console
+  log_format: "2006-01-02"   # 按天生成日志文件
+  # 区分API和Task日志
+  api_log_file: "./storage/logs/api-%s.log" # %s会被替换为日期
+  task_log_file: "./storage/logs/task-%s.log" # %s会被替换为日期
+  # 兼容旧版本配置
   log_file_name: "./storage/logs/server.log"
   max_backups: 30
-  max_age: 7
-  max_size: 1024
-  compress: true
+  max_age: 30            # 保存30天日志
+  max_size: 200          # 单个文件最大200M
+  compress: true         # 自动压缩
+  # 日志过滤和美化
+  hide_sql_args: true    # 隐藏SQL参数详情
+  hide_request_body: false # 是否隐藏请求体内容

+ 1 - 1
internal/handler/gameshield.go

@@ -79,7 +79,7 @@ func (h *GameShieldHandler) GetGameShieldKey(ctx *gin.Context) {
 		return
 	}
 
-	res, err := h.gameShieldService.GetGameShieldKey(ctx, req.Id)
+	res, err := h.gameShieldService.GetGameShieldKey(ctx, req.HostId)
 	if err != nil {
 		v1.HandleError(ctx, http.StatusInternalServerError, err, err.Error())
 		return

+ 3 - 3
internal/repository/gameshield.go

@@ -15,7 +15,7 @@ type GameShieldRepository interface {
 	GetGameShieldIsBuy(ctx context.Context, uid int64) (int64, error)
 	GetGameShieldNextduedate(ctx context.Context, uid int64, productID string) (string, error)
 	GetGameShieldNameByDunName(ctx context.Context, appName string) (string, error)
-	GetGameShieldIdByDunName(ctx context.Context, id int64) (string, error)
+	GetGameShieldHostIdByDunName(ctx context.Context, hostId string) (string, error)
 	GetGameShieldRuleIdByAppName(ctx context.Context, appName string) (int, error)
 	UpdateGameShieldByHostId(ctx context.Context, gameShield *model.GameShield) error
 	GetGameShieldByHostId(ctx context.Context, hostId int) (*model.GameShield, error)
@@ -112,10 +112,10 @@ func (r *gameShieldRepository) GetGameShieldNameByDunName(ctx context.Context, a
 	return res, nil
 }
 
-func (r *gameShieldRepository) GetGameShieldIdByDunName(ctx context.Context, id int64) (string, error) {
+func (r *gameShieldRepository) GetGameShieldHostIdByDunName(ctx context.Context, hostId string) (string, error) {
 	var res string
 	if err := r.DB(ctx).Model(&model.GameShield{}).
-		Where("id = ?", id).
+		Where("host_id = ?", hostId).
 		Pluck("dun_name", &res).Error; err != nil {
 		return "", err
 	}

+ 2 - 2
internal/service/gameshield.go

@@ -143,8 +143,8 @@ func (service *gameShieldService) DeleteGameShield(ctx context.Context, id int)
 	return res, nil
 }
 
-func (service *gameShieldService) GetGameShieldKey(ctx context.Context, id int) (string, error) {
-	dunName, err := service.gameShieldRepository.GetGameShieldIdByDunName(ctx, int64(id))
+func (service *gameShieldService) GetGameShieldKey(ctx context.Context, hostId int) (string, error) {
+	dunName, err := service.gameShieldRepository.GetGameShieldHostIdByDunName(ctx, strconv.Itoa(hostId))
 	if err != nil {
 		return "", err
 	}

+ 99 - 17
pkg/log/log.go

@@ -2,24 +2,66 @@ package log
 
 import (
 	"context"
+	"fmt"
 	"github.com/gin-gonic/gin"
 	"github.com/spf13/viper"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 	"gopkg.in/natefinch/lumberjack.v2"
 	"os"
+	"path/filepath"
 	"time"
 )
 
 const ctxLoggerKey = "zapLogger"
 
+// ServiceType 服务类型
+type ServiceType int
+
+const (
+	API  ServiceType = iota // API服务
+	Task                    // Task服务
+)
+
 type Logger struct {
 	*zap.Logger
 }
 
+// NewLog 创建一个新的日志实例,兼容旧版本配置
 func NewLog(conf *viper.Viper) *Logger {
-	// log address "out.log" User-defined
-	lp := conf.GetString("log.log_file_name")
+	return NewServiceLog(conf, API)
+}
+
+// NewServiceLog 创建特定服务类型的日志实例
+func NewServiceLog(conf *viper.Viper, serviceType ServiceType) *Logger {
+	var logPath string
+
+	// 根据服务类型和日期生成日志文件名
+	logFormat := conf.GetString("log.log_format") // 日期格式,如"2006-01-02"
+	currentDate := time.Now().Format(logFormat)
+
+	if serviceType == API && conf.IsSet("log.api_log_file") {
+		// 使用API日志路径
+		logPath = conf.GetString("log.api_log_file")
+		logPath = fmt.Sprintf(logPath, currentDate) // 替换日期占位符
+	} else if serviceType == Task && conf.IsSet("log.task_log_file") {
+		// 使用Task日志路径
+		logPath = conf.GetString("log.task_log_file")
+		logPath = fmt.Sprintf(logPath, currentDate) // 替换日期占位符
+	} else {
+		// 如果未配置专用日志路径,则使用旧配置
+		logPath = conf.GetString("log.log_file_name")
+	}
+
+	// 确保日志目录存在
+	if err := ensureDirExists(logPath); err != nil {
+		// 如果创建目录失败,回退到临时目录
+		fmt.Printf("Error creating log directory: %v, using default path\n", err)
+		logPath = "./logs/app.log"
+		ensureDirExists(logPath)
+	}
+
+	// 获取日志级别
 	lv := conf.GetString("log.log_level")
 	var level zapcore.Level
 	//debug<info<warn<error<fatal<panic
@@ -35,14 +77,17 @@ func NewLog(conf *viper.Viper) *Logger {
 	default:
 		level = zap.InfoLevel
 	}
+
+	// 日志轮转配置
 	hook := lumberjack.Logger{
-		Filename:   lp,                             // Log file path
-		MaxSize:    conf.GetInt("log.max_size"),    // Maximum size unit for each log file: M
-		MaxBackups: conf.GetInt("log.max_backups"), // The maximum number of backups that can be saved for log files
-		MaxAge:     conf.GetInt("log.max_age"),     // Maximum number of days the file can be saved
-		Compress:   conf.GetBool("log.compress"),   // Compression or not
+		Filename:   logPath,                        // 日志文件路径
+		MaxSize:    conf.GetInt("log.max_size"),    // 每个日志文件的最大大小单位:M
+		MaxBackups: conf.GetInt("log.max_backups"), // 日志文件最多保存的备份数
+		MaxAge:     conf.GetInt("log.max_age"),     // 文件最多保存的天数
+		Compress:   conf.GetBool("log.compress"),   // 是否压缩
 	}
 
+	// 配置日志编码器
 	var encoder zapcore.Encoder
 	if conf.GetString("log.encoding") == "console" {
 		encoder = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
@@ -60,7 +105,7 @@ func NewLog(conf *viper.Viper) *Logger {
 		})
 	} else {
 		encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
-			TimeKey:        "ts",
+			TimeKey:        "time",
 			LevelKey:       "level",
 			NameKey:        "logger",
 			CallerKey:      "caller",
@@ -69,24 +114,61 @@ func NewLog(conf *viper.Viper) *Logger {
 			StacktraceKey:  "stacktrace",
 			LineEnding:     zapcore.DefaultLineEnding,
 			EncodeLevel:    zapcore.LowercaseLevelEncoder,
-			EncodeTime:     zapcore.EpochTimeEncoder,
-			EncodeDuration: zapcore.SecondsDurationEncoder,
+			EncodeTime:     timeEncoder, // 使用自定义时间格式
+			EncodeDuration: zapcore.StringDurationEncoder,
 			EncodeCaller:   zapcore.ShortCallerEncoder,
 		})
 	}
-	core := zapcore.NewCore(
-		encoder,
-		zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // Print to console and file
-		level,
+
+	// 创建日志核心
+	logOutput := zapcore.NewMultiWriteSyncer(
+		zapcore.AddSync(os.Stdout), // 同时输出到控制台
+		zapcore.AddSync(&hook),     // 和文件
 	)
+	core := zapcore.NewCore(encoder, logOutput, level)
+
+	// 开发环境与生产环境的差异化配置
+	var logger *zap.Logger
 	if conf.GetString("env") != "prod" {
-		return &Logger{zap.New(core, zap.Development(), zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
+		logger = zap.New(core,
+			zap.Development(),
+			zap.AddCaller(),
+			zap.AddStacktrace(zap.ErrorLevel),
+		)
+	} else {
+		logger = zap.New(core,
+			zap.AddCaller(),
+			zap.AddStacktrace(zap.ErrorLevel),
+		)
+	}
+
+	// 添加服务类型标识
+	serviceTypeField := zap.String("service", serviceTypeToString(serviceType))
+	logger = logger.With(serviceTypeField)
+
+	return &Logger{logger}
+}
+
+// ensureDirExists 确保日志目录存在
+func ensureDirExists(filePath string) error {
+	dir := filepath.Dir(filePath)
+	return os.MkdirAll(dir, 0755)
+}
+
+// serviceTypeToString 将服务类型转换为字符串
+func serviceTypeToString(serviceType ServiceType) string {
+	switch serviceType {
+	case API:
+		return "api"
+	case Task:
+		return "task"
+	default:
+		return "unknown"
 	}
-	return &Logger{zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
 }
 
+// timeEncoder 自定义时间编码器,提供高精度时间戳
 func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
-	//enc.AppendString(t.Format("2006-01-02 15:04:05"))
 	enc.AppendString(t.Format("2006-01-02 15:04:05.000000000"))
 }