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

Gin 结构化日志与字段注入

结构化日志将日志信息以键值对形式组织,便于日志系统解析和查询。

结构化日志基础

基本字段添加

Go
import "github.com/sirupsen/logrus"

var logger = logrus.New()

// 使用 WithFields 添加多个字段
logger.WithFields(logrus.Fields{
    "event":   "user_login",
    "user_id": 12345,
    "source":  "web",
}).Info("用户登录成功")

// 使用 WithField 添加单个字段
logger.WithField("request_id", "abc123").
    WithField("duration_ms", 150).
    Info("请求处理完成")

// 输出示例(JSON格式)
// {"level":"info","event":"user_login","user_id":12345,"source":"web","time":"2026-05-18T10:30:00Z","message":"用户登录成功"}

字段链式添加

Go
// 创建带基础字段的对象
baseLogger := logger.WithFields(logrus.Fields{
    "app":    "gin-api",
    "version": "1.0.0",
    "env":    "production",
})

// 在此基础上添加更多字段
requestLogger := baseLogger.WithFields(logrus.Fields{
    "request_id": "abc123",
    "path":       "/api/users",
})

// 使用
requestLogger.Info("请求开始")
requestLogger.WithField("status", 200).Info("请求完成")

Gin 中间件字段注入

请求基础字段注入

Go
func RequestFieldsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 生成请求 ID
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }
        c.Set("request_id", requestID)
        c.Header("X-Request-ID", requestID)

        // 创建带请求字段的 logger
        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":  time.Since(startTime).Milliseconds(),
            "response_size": c.Writer.Size(),
        }).Info("请求完成")
    }
}

用户字段注入

Go
func UserFieldsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 获取基础 logger
        baseLogger := c.MustGet("logger").(*logrus.Entry)

        // 认证后添加用户字段
        userID, exists := c.Get("user_id")
        if exists {
            ctxLogger := baseLogger.WithFields(logrus.Fields{
                "user_id":  userID,
                "username": c.GetString("username"),
            })
            c.Set("logger", ctxLogger)
        }

        c.Next()
    }
}

// 认证中间件配合使用
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        claims, err := ParseToken(token)
        if err != nil {
            c.JSON(401, gin.H{"error": "未认证"})
            c.Abort()
            return
        }

        c.Set("user_id", claims.UserID)
        c.Set("username", claims.Username)
        c.Next()
    }
}

Handler 中使用

Go
func GetUserHandler(c *gin.Context) {
    ctxLogger := c.MustGet("logger").(*logrus.Entry)
    userID := c.Param("id")

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

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

    ctxLogger.WithFields(logrus.Fields{
        "target_user_id": userID,
        "user_name":      user.Name,
    }).Info("查询成功")

    c.JSON(200, user)
}

func CreateOrderHandler(c *gin.Context) {
    ctxLogger := c.MustGet("logger").(*logrus.Entry)

    var req OrderRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        ctxLogger.WithError(err).Warn("参数解析失败")
        c.JSON(400, gin.H{"error": "参数错误"})
        return
    }

    order, err := createOrder(req)
    if err != nil {
        ctxLogger.WithError(err).WithFields(logrus.Fields{
            "product_id": req.ProductID,
            "quantity":   req.Quantity,
        }).Error("创建订单失败")
        c.JSON(500, gin.H{"error": "创建失败"})
        return
    }

    ctxLogger.WithFields(logrus.Fields{
        "order_id":   order.ID,
        "amount":     order.Amount,
        "product_id": req.ProductID,
    }).Info("订单创建成功")

    c.JSON(200, order)
}

自定义 Hook 扩展

添加固定字段 Hook

Go
type ContextHook struct {
    AppName    string
    AppVersion string
    Env        string
}

func (hook *ContextHook) Fire(entry *logrus.Entry) error {
    entry.Data["app"] = hook.AppName
    entry.Data["version"] = hook.AppVersion
    entry.Data["env"] = hook.Env
    return nil
}

func (hook *ContextHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func SetupLogger() {
    logger.SetFormatter(&logrus.JSONFormatter{})
    logger.AddHook(&ContextHook{
        AppName:    "gin-api",
        AppVersion: "1.0.0",
        Env:        "production",
    })
}

// 所有日志自动包含 app/version/env 字段

请求追踪 Hook

Go
type TraceHook struct{}

func (hook *TraceHook) Fire(entry *logrus.Entry) error {
    // 如果存在 gin.Context,添加追踪信息
    if ctx, ok := entry.Data["gin_context"]; ok {
        ginCtx := ctx.(*gin.Context)
        entry.Data["request_id"] = ginCtx.GetString("request_id")
        entry.Data["trace_id"] = ginCtx.GetString("trace_id")
    }
    return nil
}

func (hook *TraceHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

// 在中间件中传递 gin.Context
func TraceLoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        logger.WithField("gin_context", c).Info("请求处理")
        c.Next()
    }
}

字段命名规范

标准字段名

Go
// 推荐使用的标准字段名
const (
    FieldRequestID    = "request_id"
    FieldUserID       = "user_id"
    FieldUsername     = "username"
    FieldMethod       = "method"
    FieldPath         = "path"
    FieldStatusCode   = "status_code"
    FieldLatencyMs    = "latency_ms"
    FieldClientIP     = "client_ip"
    FieldError        = "error"
    FieldErrorCode    = "error_code"
    FieldTimestamp    = "timestamp"
)

// 使用示例
logger.WithFields(logrus.Fields{
    FieldRequestID:  "abc123",
    FieldUserID:     12345,
    FieldStatusCode: 200,
    FieldLatencyMs:  150,
}).Info("请求完成")

字段类型规范

Go
// 字段值应使用标准类型,便于解析
logger.WithFields(logrus.Fields{
    "user_id":       12345,           // int(推荐)
    "amount":        99.99,           // float
    "is_admin":      true,            // bool
    "username":      "john",          // string
    "created_at":    "2026-05-18",    // string(时间格式化)
    "tags":          []string{"vip"}, // slice
    "metadata":      map[string]string{"region": "cn"}, // map
})

日志查询示例

Elasticsearch 查询

JSON
// 查找特定请求的所有日志
{
  "query": {
    "term": {
      "request_id": "abc123"
    }
  }
}

// 查找特定用户的错误日志
{
  "query": {
    "bool": {
      "must": [
        {"term": {"user_id": "12345"}},
        {"term": {"level": "error"}}
      ]
    }
  }
}

字段注入流程

text
请求进入 → RequestIDMiddleware → UserFieldsMiddleware → Handler
    ↓            ↓                      ↓                   ↓
  基础字段    请求字段               用户字段            业务字段
    ↓            ↓                      ↓                   ↓
 app/version  request_id/method       user_id            order_id/product_id

注意:字段名保持一致,便于跨系统查询和关联。

要点总结

  1. WithFields:添加多个字段,链式调用叠加字段
  2. 中间件注入:统一添加请求 ID、用户 ID 等公共字段
  3. 上下文传递:带字段的 logger 存入 Gin Context
  4. Handler 使用:从上下文获取 logger,添加业务字段
  5. Hook 扩展:自动添加固定字段,如应用名、版本
  6. 命名规范:统一字段名和值类型,便于解析查询

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

← 上一篇 Gin 日志链路追踪与请求ID
下一篇 → Gin 自定义中间件记录请求日志
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

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

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