// 请求日志中间件:记录请求ID、路由、客户端IP等关键信息 package middleware import ( "context" "fmt" "simple-memo/global" "simple-memo/utils" "strconv" "time" "github.com/gin-gonic/gin" "github.com/sony/sonyflake" "go.uber.org/zap" ) // requestIDSF 用于生成全局唯一的请求ID var requestIDSF *sonyflake.Sonyflake func init() { requestIDSF = sonyflake.NewSonyflake(sonyflake.Settings{}) } // genRequestID 基于 sonyflake 生成请求ID(base36 紧凑字符串) func genRequestID() string { if requestIDSF == nil { return fmt.Sprintf("%x", time.Now().UnixNano()) } id, err := requestIDSF.NextID() if err != nil { return fmt.Sprintf("%x", time.Now().UnixNano()) } // base36 编码:紧凑且只含 [0-9a-z] return strconv.FormatUint(id, 36) } // RequestLog 请求日志中间件 func RequestLog() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() // 优先从请求头获取 request-id,否则自动生成 requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = genRequestID() } // 客户端IP clientIP := c.ClientIP() // 路由路径 route := c.Request.URL.Path // 请求方法 method := c.Request.Method // 写入标准 context,供整条链路使用 ctx := context.WithValue(c.Request.Context(), utils.RequestIdKey, requestID) ctx = context.WithValue(ctx, utils.RequestRouteKey, route) ctx = context.WithValue(ctx, utils.ClientIPKey, clientIP) c.Request = c.Request.WithContext(ctx) // 同时记录到 gin context,兼容老代码 c.Set("request_id", requestID) c.Set("client_ip", clientIP) c.Set("route", route) // 打印请求进入日志 global.Logger.Info("【请求进入】", zap.String("request_id", requestID), zap.String("route", route), zap.String("method", method), zap.String("client_ip", clientIP), zap.String("user_agent", c.Request.UserAgent()), ) c.Next() // 请求结束后打印耗时日志 duration := time.Since(start) global.Logger.Info("【请求结束】", zap.String("request_id", requestID), zap.String("route", route), zap.Int("status", c.Writer.Status()), zap.Duration("duration", duration), ) } }