13--日志组件
Go在1.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"}

浙公网安备 33010602011771号