全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 8 分钟 ✍️ juanwangdev

Gin 自定义中间件记录请求日志

自定义日志中间件比 Gin 默认中间件更灵活,可根据业务需求定制日志内容。

基础请求日志中间件

简单实现

Go
func RequestLogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        startTime := time.Now()

        // 记录请求信息
        logger.WithFields(logrus.Fields{
            "method":     c.Request.Method,
            "path":       c.Request.URL.Path,
            "query":      c.Request.URL.RawQuery,
            "client_ip":  c.ClientIP(),
            "user_agent": c.Request.UserAgent(),
        }).Info("请求开始")

        c.Next()

        // 记录响应信息
        latency := time.Since(startTime)
        logger.WithFields(logrus.Fields{
            "method":       c.Request.Method,
            "path":         c.Request.URL.Path,
            "status":       c.Writer.Status(),
            "latency_ms":   latency.Milliseconds(),
            "response_size": c.Writer.Size(),
        }).Info("请求完成")
    }
}

func main() {
    r := gin.New()
    r.Use(RequestLogMiddleware())
    r.Run(":8080")
}

详细请求日志

Go
func DetailedRequestLogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        startTime := time.Now()

        // 请求基本信息
        fields := logrus.Fields{
            "request_id":   uuid.New().String(),
            "method":       c.Request.Method,
            "path":         c.Request.URL.Path,
            "query":        c.Request.URL.RawQuery,
            "client_ip":    c.ClientIP(),
            "user_agent":   c.Request.UserAgent(),
            "content_type": c.GetHeader("Content-Type"),
            "referer":      c.GetHeader("Referer"),
        }

        // 记录请求体(可选)
        if c.Request.Method != "GET" && c.Request.ContentLength > 0 && c.Request.ContentLength < 1024 {
            bodyBytes, _ := io.ReadAll(c.Request.Body)
            c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

            // 过滤敏感字段
            filteredBody := filterSensitiveFields(string(bodyBytes))
            fields["body"] = filteredBody
        }

        c.Set("request_id", fields["request_id"])
        c.Set("start_time", startTime)

        logger.WithFields(fields).Info("请求开始")

        c.Next()

        // 响应信息
        latency := time.Since(startTime)
        fields["status"] = c.Writer.Status()
        fields["latency_ms"] = latency.Milliseconds()
        fields["response_size"] = c.Writer.Size()

        // 根据状态码分级记录
        if c.Writer.Status() >= 500 {
            logger.WithFields(fields).Error("请求失败")
        } else if c.Writer.Status() >= 400 {
            logger.WithFields(fields).Warn("客户端错误")
        } else {
            logger.WithFields(fields).Info("请求完成")
        }
    }
}

敏感信息过滤

参数过滤

Go
var sensitiveParams = []string{"password", "token", "secret", "api_key", "credit_card"}

func filterSensitiveFields(data string) string {
    var result string
    jsonData := make(map[string]interface{})

    if err := json.Unmarshal([]byte(data), &jsonData); err == nil {
        for k, v := range jsonData {
            if isSensitiveField(k) {
                jsonData[k] = "[FILTERED]"
            }
        }
        filtered, _ := json.Marshal(jsonData)
        result = string(filtered)
    } else {
        // 非 JSON 格式,简单替换
        result = data
        for _, field := range sensitiveParams {
            // 简化处理:替换常见格式
            result = regexp.MustCompile(field+`=\w+`).
                ReplaceAllString(result, field+"=[FILTERED]")
        }
    }

    return result
}

func isSensitiveField(field string) bool {
    fieldLower := strings.ToLower(field)
    for _, sensitive := range sensitiveParams {
        if strings.Contains(fieldLower, sensitive) {
            return true
        }
    }
    return false
}

Query 参数过滤

Go
func filterQueryParams(query string) string {
    if query == "" {
        return query
    }

    params := strings.Split(query, "&")
    filtered := make([]string, 0)

    for _, param := range params {
        parts := strings.SplitN(param, "=", 2)
        if len(parts) == 2 && isSensitiveField(parts[0]) {
            filtered = append(filtered, parts[0]+"=[FILTERED]")
        } else {
            filtered = append(filtered, param)
        }
    }

    return strings.Join(filtered, "&")
}

响应内容记录

响应体捕获

Go
type responseWriter struct {
    gin.ResponseWriter
    body *bytes.Buffer
}

func (w *responseWriter) Write(b []byte) (int, error) {
    w.body.Write(b)
    return w.ResponseWriter.Write(b)
}

func ResponseLogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 包装 ResponseWriter
        writer := &responseWriter{
            ResponseWriter: c.Writer,
            body:           bytes.NewBufferString(""),
        }
        c.Writer = writer

        startTime := time.Now()
        c.Next()

        latency := time.Since(startTime)

        // 记录响应信息
        responseBody := writer.body.String()

        // 限制响应体长度
        if len(responseBody) > 500 {
            responseBody = responseBody[:500] + "...[truncated]"
        }

        logger.WithFields(logrus.Fields{
            "request_id":    c.GetString("request_id"),
            "status":        c.Writer.Status(),
            "latency_ms":    latency.Milliseconds(),
            "response_size": c.Writer.Size(),
            "response_body": responseBody,
        }).Info("响应内容")
    }
}

慢请求记录

Go
func SlowRequestLogMiddleware(thresholdMs int64) gin.HandlerFunc {
    return func(c *gin.Context) {
        startTime := time.Now()
        c.Next()

        latency := time.Since(startTime).Milliseconds()

        // 慢请求告警
        if latency > thresholdMs {
            logger.WithFields(logrus.Fields{
                "request_id": c.GetString("request_id"),
                "method":     c.Request.Method,
                "path":       c.Request.URL.Path,
                "latency_ms": latency,
                "threshold_ms": thresholdMs,
                "client_ip":  c.ClientIP(),
            }).Warn("慢请求警告")
        }
    }
}

func main() {
    r := gin.New()
    r.Use(RequestIDMiddleware())
    r.Use(RequestLogMiddleware())
    r.Use(SlowRequestLogMiddleware(500)) // 超过 500ms 告警
}

错误请求记录

Go
func ErrorRequestLogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()

        // 检查是否有错误
        if len(c.Errors) > 0 {
            requestID := c.GetString("request_id")

            for _, err := range c.Errors {
                logger.WithFields(logrus.Fields{
                    "request_id":  requestID,
                    "error_type":  err.Type,
                    "error_msg":   err.Error(),
                    "error_meta":  err.Meta,
                    "path":        c.Request.URL.Path,
                    "method":      c.Request.Method,
                    "client_ip":   c.ClientIP(),
                }).Error("请求错误")
            }
        }

        // 4xx/5xx 错误记录
        status := c.Writer.Status()
        if status >= 400 {
            logger.WithFields(logrus.Fields{
                "request_id": c.GetString("request_id"),
                "status":     status,
                "path":       c.Request.URL.Path,
                "method":     c.Request.Method,
            }).Error("HTTP错误响应")
        }
    }
}

请求统计

Go
type RequestStats struct {
    TotalRequests   int64
    SuccessRequests int64
    ErrorRequests   int64
    AvgLatencyMs    int64
    PathStats       map[string]*PathStat
}

type PathStat struct {
    Count     int64
    AvgLatency int64
    MaxLatency int64
    Errors    int64
}

var stats = &RequestStats{
    PathStats: make(map[string]*PathStat),
}

func StatsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        startTime := time.Now()
        path := c.Request.URL.Path

        c.Next()

        latency := time.Since(startTime).Milliseconds()
        status := c.Writer.Status()

        // 更新统计
        stats.TotalRequests++
        if status < 400 {
            stats.SuccessRequests++
        } else {
            stats.ErrorRequests++
        }

        // 路径统计
        pathStat, exists := stats.PathStats[path]
        if !exists {
            pathStat = &PathStat{}
            stats.PathStats[path] = pathStat
        }

        pathStat.Count++
        pathStat.AvgLatency = (pathStat.AvgLatency*(pathStat.Count-1) + latency) / pathStat.Count
        if latency > pathStat.MaxLatency {
            pathStat.MaxLatency = latency
        }
        if status >= 400 {
            pathStat.Errors++
        }
    }
}

// 统计接口
func GetStatsHandler(c *gin.Context) {
    c.JSON(200, stats)
}

完整日志中间件配置

Go
func SetupLogMiddleware(r *gin.Engine) {
    // 请求 ID
    r.Use(RequestIDMiddleware())

    // 基础请求日志
    r.Use(RequestLogMiddleware())

    // 慢请求告警(根据业务调整阈值)
    r.Use(SlowRequestLogMiddleware(1000))

    // 错误请求记录
    r.Use(ErrorRequestLogMiddleware())

    // 请求统计
    r.Use(StatsMiddleware())

    // 管理接口(查看统计)
    r.GET("/admin/stats", AuthMiddleware(), GetStatsHandler)
}

日志字段对比

字段记录时机用途
request_id请求开始链路追踪
method请求开始区分操作类型
path请求开始资源定位
client_ip请求开始来源追踪
latency_ms请求结束性能分析
status请求结束成功/失败判断
error_msg异常时问题定位

注意:敏感参数必须过滤,避免泄露用户密码、token 等。

要点总结

  1. 请求信息:method、path、query、client_ip、user_agent
  2. 响应信息:status、latency、response_size
  3. 敏感过滤:password、token 等字段替换为 [FILTERED]
  4. 慢请求告警:超过阈值自动记录告警
  5. 错误记录:4xx/5xx 错误详细记录
  6. 请求统计:路径级别统计便于分析

📝 发现内容有误?点击此处直接编辑

← 上一篇 Gin 结构化日志与字段注入
下一篇 → Gin 错误日志收集与告警
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库