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,否则无法关联日志。
要点总结
- 请求 ID 生成:使用 UUID 或自定义格式,支持上游传递
- 上下文存储:将请求 ID 存入 Gin Context 供全局使用
- 日志关联:每条日志包含请求 ID,便于追踪定位
- 跨服务传递:HTTP 调用时通过 Header 传递追踪信息
- 结构化输出:使用 JSON 格式便于日志系统解析
- 错误追踪:统一捕获和记录请求过程中的错误
📝 发现内容有误?点击此处直接编辑