13--日志组件

Go1.21更新中,为标准库引入了slog,我们可以利用slog简单实现日志组件

一. slog初始化

cmd/secgo-mall/main.go

// ... 原代码 ...

import (
	"os"
	"log/slog"
	// ... 原代码 ...
)

func main() {
	// 初始化slog
	// 设置默认日志处理器为 JSON 格式,输出到标准输出
	// 设置日志级别为 INFO
	slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	})))

	slog.Info("starting server", "port", config.GlobalConfig.Server.Port)

	// ... 原代码 ...
}

// ... 原代码 ...

SetDefault 将 slog 设为主日志器,之后项目任意位置直接调用 slog.Info() / slog.Error() 即可输出结构化 JSON 日志。

Gin 使用 gin.Default() 创建引擎,它已自带 Logger()Recovery() 中间件,负责打印每个请求的路由、状态码、响应时间等,不需要额外挂载。

二. 日志替换

1) Redis

internal/cache/redis.go

func InitRedis() {
	// ... 原代码 ...

	ctx := context.Background()
	if err := Rdb.Ping(ctx).Err(); err != nil {
		// panic(fmt.Sprintf("Failed to connect to Redis: %v", err))
		slog.Error("redis connect failed", "error", err.Error())
	}
	// println("Connected to Redis successfully")
	slog.Info("redis connected", "host", rCfg.Host, "port", rCfg.Port)
}

2) RabbitMQ

internal/mq/rabbitmq.go

// ... 代码 ...

// InitRabbitMQ 初始化RabbitMQ连接和通道
func InitRabbitMQ() {
	// ... 原代码 ...
	
	var err error
	// 建立TCP连接
	Conn, err = amqp.Dial(url)
	if err != nil {
		// 原:panic("RabbitMQ connection error:" + err.Error())
		slog.Error("RabbitMQ connection error", "error", err.Error())
		os.Exit(1)
	}

	// ... 原代码 ...
	
	// 原: println("RabbitMQ connected successful")
	slog.Info("RabbitMQ connected successful",
		"host", rCfg.Host,
		"port", rCfg.Port,
		"vhost", rCfg.Vhost,
	)
}

3) 限流中间件

`internal/middleware/ratelimit.go

// RateLimit 限流中间件
// 使用 Redis INCR + EXPIRE 实现滑动窗口限流
// 参数 limit: 每秒最多允许的请求次数
func RateLimit(limit int) gin.HandlerFunc {
	return func(c *gin.Context) {
		// ... 原代码 ...
		
		// 超过限制,拦截请求
		if count > int64(limit) {
			// 超出限流阈
			c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
				"code":    429,
				"message": "too many requests, please try again later",
			})

			// 新 限流触发输出日志提醒
			slog.Warn("rate limit triggered",
				"uid", uid,
				"key", key,
				"count", count,
			)

			return
		}

		c.Next()
	}
}

4) RBAC鉴权中间件

internal/middleware/auth.go

// JWTAuth 是一个Gin中间件函数,用于验证请求中的JWT token是否合法
func JWTAuth() gin.HandlerFunc {
		return func(c *gin.Context) {
		// ... 原代码 ...
	
		claims, err := jwtx.ParseToken(parts[1], config.GlobalConfig.JWT.Secret) // 调用jwtx包中的ParseToken函数,传入JWT token和配置中的JWT签名密钥进行解析
		// 如果解析过程中发生错误,返回401 Unauthorized错误
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "invalid token: " + err.Error()})
			slog.Warn("JWT parse failed", "error", err.Error()) //新
			return
		}
	
		// ... 原代码 ...
		}
}

5) 秒杀service日志

internal/service/seckill.go

// ... 原代码 ...

// SeckillPurchase 秒杀活动下单逻辑
// 返回值 1: 成功, 0: 库存不足, -1: 系统错误, -2: 活动未开始, -3: 活动已结束
func SeckillPurchase(seckillId, userId uint) (int, error) {
	// ... 原代码 ...

	if err := mq.SendOrderMessage(orderMsg); err != nil {
		// MQ 发送失败日志告警(库存已扣,不应回滚)
		// 原:println("MQ send failed:", err.Error())
		slog.Warn("MQ send failed",
			"seckill_id", seckillId,
			"user_id", userId,
			"error", err.Error(),
		)
	}

	return 1, nil // 返回订单创建成功信息
}

三. 验证

config.yaml中将server.mode改为release方便观察

go run cmd/secgo-mall/main.go

PS C:\Code\Projects\Secgo-Mall> go run .\cmd\secgo-mall\main.go
{"time":"2026-04-16T12:13:49.7061583+08:00","level":"INFO","msg":"starting server","port":8080}
{"time":"2026-04-16T12:13:49.7200782+08:00","level":"INFO","msg":"MySQL connected successfully."}
{"time":"2026-04-16T12:13:49.7521452+08:00","level":"INFO","msg":"redis connected","host":"127.0.0.1","port":6379}
{"time":"2026-04-16T12:13:49.7579522+08:00","level":"INFO","msg":"RabbitMQ connected successful","host":"127.0.0.1","port":5672,"vhost":"/"}
{"time":"2026-04-16T12:13:49.7590866+08:00","level":"INFO","msg":"Starting Secgo-Mall server on :8080"}
posted @ 2026-04-17 17:47  Chuan81  阅读(9)  评论(0)    收藏  举报