Skip to content

日志记录

VEF Framework 中的日志记录最佳实践。

日志配置

yaml
# config.yaml
log:
  level: "info"        # debug, info, warn, error
  format: "json"       # json or text
  output: "stdout"     # stdout or file path
  file:
    enabled: false
    path: "logs/app.log"
    maxSize: 100       # MB
    maxBackups: 3
    maxAge: 7          # days

基本用法

导入日志包

go
import "github.com/ilxqx/vef-framework-go/log"

日志级别

go
log.Debug("Debug message")
log.Info("Info message")
log.Warn("Warning message")
log.Error("Error message")

带字段的日志

go
log.WithFields(log.Fields{
    "userId":    user.Id,
    "username":  user.Username,
    "action":    "login",
}).Info("User logged in")

带错误的日志

go
if err != nil {
    log.WithError(err).Error("Failed to create user")
}

请求日志

记录请求上下文

go
func (r *UserResource) CreateUser(ctx fiber.Ctx, params UserParams) error {
    requestId := contextx.GetRequestId(ctx)
    currentUser := contextx.GetCurrentUser(ctx)
    
    logger := log.WithFields(log.Fields{
        "requestId": requestId,
        "operator":  currentUser.Username,
        "action":    "create_user",
    })
    
    logger.Info("Creating user")
    
    // ... create user
    
    logger.WithField("userId", user.Id).Info("User created")
    
    return api.Success(ctx, user)
}

结构化日志

定义日志结构

go
type AuditLog struct {
    Action     string      `json:"action"`
    Resource   string      `json:"resource"`
    ResourceId string      `json:"resourceId"`
    Operator   string      `json:"operator"`
    Changes    interface{} `json:"changes,omitempty"`
    Timestamp  time.Time   `json:"timestamp"`
}

func LogAudit(ctx fiber.Ctx, action, resource, resourceId string, changes interface{}) {
    currentUser := contextx.GetCurrentUser(ctx)
    
    log.WithFields(log.Fields{
        "audit":      true,
        "action":     action,
        "resource":   resource,
        "resourceId": resourceId,
        "operator":   currentUser.Username,
        "changes":    changes,
    }).Info("Audit log")
}

日志中间件

请求日志中间件

go
func RequestLoggerMiddleware() fiber.Handler {
    return func(ctx *fiber.Ctx) error {
        start := time.Now()
        
        // Process request
        err := ctx.Next()
        
        // Log request
        duration := time.Since(start)
        
        fields := log.Fields{
            "method":    ctx.Method(),
            "path":      ctx.Path(),
            "status":    ctx.Response().StatusCode(),
            "duration":  duration.String(),
            "ip":        ctx.IP(),
            "userAgent": ctx.Get("User-Agent"),
            "requestId": contextx.GetRequestId(ctx),
        }
        
        if currentUser := contextx.GetCurrentUser(ctx); currentUser != nil {
            fields["userId"] = currentUser.Id
            fields["username"] = currentUser.Username
        }
        
        if err != nil {
            fields["error"] = err.Error()
            log.WithFields(fields).Error("Request failed")
        } else {
            log.WithFields(fields).Info("Request completed")
        }
        
        return err
    }
}

敏感数据处理

过滤敏感字段

go
func sanitizeLogFields(fields log.Fields) log.Fields {
    sensitiveFields := []string{"password", "token", "secret", "apiKey"}
    
    for _, field := range sensitiveFields {
        if _, exists := fields[field]; exists {
            fields[field] = "[REDACTED]"
        }
    }
    
    return fields
}

安全日志记录

go
func LogUserAction(ctx fiber.Ctx, action string, data interface{}) {
    fields := log.Fields{
        "action":    action,
        "requestId": contextx.GetRequestId(ctx),
        "userId":    contextx.GetCurrentUser(ctx).Id,
    }
    
    // Sanitize data before logging
    if dataMap, ok := data.(map[string]interface{}); ok {
        sanitizedData := make(map[string]interface{})
        for k, v := range dataMap {
            if k == "password" || k == "token" {
                sanitizedData[k] = "[REDACTED]"
            } else {
                sanitizedData[k] = v
            }
        }
        fields["data"] = sanitizedData
    }
    
    log.WithFields(fields).Info("User action")
}

日志轮转

配置文件轮转

yaml
log:
  file:
    enabled: true
    path: "logs/app.log"
    maxSize: 100      # 100MB per file
    maxBackups: 10    # Keep 10 backup files
    maxAge: 30        # Keep files for 30 days
    compress: true    # Compress old files

生产环境建议

  1. 使用 JSON 格式:便于日志聚合和分析
  2. 设置适当的日志级别:生产环境使用 infowarn
  3. 包含请求 ID:便于追踪请求链路
  4. 避免记录敏感数据:密码、Token 等
  5. 使用结构化日志:便于搜索和过滤
yaml
# Production logging configuration
log:
  level: "info"
  format: "json"
  output: "stdout"  # Let container runtime handle log collection

下一步

基于 Apache License 2.0 许可发布