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

Gin 日志链路追踪与请求ID

请求 ID 是分布式系统中追踪请求的关键标识,贯穿整个请求生命周期。

请求 ID 生成与传递

中间件生成请求 ID

Go
import (
    "github.com/google/uuid"
)

func RequestIDMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从请求头获取(支持上游传递)
        requestID := c.GetHeader("X-Request-ID")

        // 如果不存在,生成新的
        if requestID == "" {
            requestID = uuid.New().String()
        }

        // 存入上下文
        c.Set("request_id", requestID)

        // 设置响应头
        c.Header("X-Request-ID", requestID)

        c.Next()
    }
}

func main() {
    r := gin.Default()
    r.Use(RequestIDMiddleware())
    r.Run(":8080")
}

自定义 ID 格式

Go
import (
    "crypto/rand"
    "encoding/hex"
    "time"
)

// 短 ID(适合日志显示)
func GenerateShortID() string {
    b := make([]byte, 4)
    rand.Read(b)
    return hex.EncodeToString(b)
}

// 时间戳 + 随机数格式
func GenerateTimestampID() string {
    timestamp := time.Now().Format("20060102150405")
    random := GenerateShortID()
    return timestamp + "-" + random
}

func RequestIDMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = GenerateTimestampID()
        }
        c.Set("request_id", requestID)
        c.Header("X-Request-ID", requestID)
        c.Next()
    }
}

日志关联请求 ID

结构化日志集成

Go
import (
    "github.com/sirupsen/logrus"
)

var logger = logrus.New()

func ContextLoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        requestID := c.GetString("request_id")

        // 创建带上下文的日志实例
        ctxLogger := logger.WithFields(logrus.Fields{
            "request_id":  requestID,
            "method":      c.Request.Method,
            "path":        c.Request.URL.Path,
            "client_ip":   c.ClientIP(),
            "user_agent":  c.Request.UserAgent(),
        })

        // 存入上下文供后续使用
        c.Set("logger", ctxLogger)

        // 记录请求开始
        ctxLogger.Info("请求开始")

        c.Next()

        // 记录请求结束
        ctxLogger.WithFields(logrus.Fields{
            "status":      c.Writer.Status(),
            "latency_ms":  c.Writer.Size(),
        }).Info("请求完成")
    }
}

在 Handler 中使用

Go
func GetUserHandler(c *gin.Context) {
    // 获取带上下文的 logger
    ctxLogger := c.MustGet("logger").(*logrus.Entry)

    userID := c.Param("id")

    ctxLogger.WithField("user_id", userID).Info("查询用户")

    user, err := getUser(userID)
    if err != nil {
        ctxLogger.WithError(err).Error("查询失败")
        c.JSON(500, gin.H{"error": "查询失败"})
        return
    }

    ctxLogger.Info("查询成功")
    c.JSON(200, user)
}

跨服务传递

HTTP 调用传递

Go
import (
    "net/http"
)

func CallExternalService(c *gin.Context, url string) ([]byte, error) {
    requestID := c.GetString("request_id")

    // 创建请求并传递请求 ID
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }

    // 设置请求 ID 头
    req.Header.Set("X-Request-ID", requestID)

    // 发送请求
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

传递链路追踪信息

Go
func TraceMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 请求 ID
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }

        // 父请求 ID(嵌套调用场景)
        parentID := c.GetHeader("X-Parent-ID")

        // 生成当前请求 ID
        currentID := uuid.New().String()

        // 存入上下文
        c.Set("trace_id", requestID)       // 全局追踪 ID
        c.Set("span_id", currentID)        // 当前跨度 ID
        c.Set("parent_id", parentID)       // 父跨度 ID

        // 设置响应头
        c.Header("X-Trace-ID", requestID)
        c.Header("X-Span-ID", currentID)

        c.Next()
    }
}

日志格式化输出

Go
func SetupLogger() {
    logger.SetFormatter(&logrus.JSONFormatter{
        FieldMap: logrus.FieldMap{
            logrus.FieldKeyTime:  "timestamp",
            logrus.FieldKeyLevel: "level",
            logrus.FieldKeyMsg:   "message",
        },
    })

    logger.SetLevel(logrus.InfoLevel)
}

// 日志输出示例
// {
//   "timestamp": "2026-05-18T10:30:00Z",
//   "level": "info",
//   "message": "请求开始",
//   "request_id": "abc123",
//   "method": "GET",
//   "path": "/api/users",
//   "client_ip": "192.168.1.1"
// }

完整追踪示例

Go
func main() {
    r := gin.New()

    // 配置日志
    SetupLogger()

    // 请求追踪中间件
    r.Use(RequestIDMiddleware())
    r.Use(TraceMiddleware())
    r.Use(ContextLoggerMiddleware())

    r.GET("/api/users/:id", GetUserHandler)
    r.GET("/api/orders/:id", func(c *gin.Context) {
        ctxLogger := c.MustGet("logger").(*logrus.Entry)

        // 调用外部服务并传递追踪 ID
        userData, err := CallExternalService(c, "http://user-service/users/"+c.Param("id"))
        if err != nil {
            ctxLogger.WithError(err).Error("调用用户服务失败")
            c.JSON(500, gin.H{"error": "服务调用失败"})
            return
        }

        ctxLogger.Info("订单查询成功")
        c.JSON(200, gin.H{"user": userData})
    })

    r.Run(":8080")
}

错误追踪增强

Go
func ErrorTraceMiddleware() 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_meta": err.Meta,
                }).Error(err.Error())
            }
        }
    }
}

追踪信息对比

字段说明来源
X-Request-ID全局请求标识生成或上游传递
X-Trace-ID分布式追踪标识整个链路共用
X-Span-ID当前跨度标识每个服务独立
X-Parent-ID父跨度标识上游服务传递

注意:跨服务调用必须传递追踪 ID,否则无法关联日志。

要点总结

  1. 请求 ID 生成:使用 UUID 或自定义格式,支持上游传递
  2. 上下文存储:将请求 ID 存入 Gin Context 供全局使用
  3. 日志关联:每条日志包含请求 ID,便于追踪定位
  4. 跨服务传递:HTTP 调用时通过 Header 传递追踪信息
  5. 结构化输出:使用 JSON 格式便于日志系统解析
  6. 错误追踪:统一捕获和记录请求过程中的错误

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

← 上一篇 Gin 日志输出到文件与轮转
下一篇 → Gin 结构化日志与字段注入
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

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

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