日志记录
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生产环境建议
- 使用 JSON 格式:便于日志聚合和分析
- 设置适当的日志级别:生产环境使用
info或warn - 包含请求 ID:便于追踪请求链路
- 避免记录敏感数据:密码、Token 等
- 使用结构化日志:便于搜索和过滤
yaml
# Production logging configuration
log:
level: "info"
format: "json"
output: "stdout" # Let container runtime handle log collection