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
注意:字段名保持一致,便于跨系统查询和关联。
要点总结
- WithFields:添加多个字段,链式调用叠加字段
- 中间件注入:统一添加请求 ID、用户 ID 等公共字段
- 上下文传递:带字段的 logger 存入 Gin Context
- Handler 使用:从上下文获取 logger,添加业务字段
- Hook 扩展:自动添加固定字段,如应用名、版本
- 命名规范:统一字段名和值类型,便于解析查询
📝 发现内容有误?点击此处直接编辑