Skip to content

日志内容规范

日志格式要求

结构化日志

必须使用结构化格式(键值对),禁止使用纯文本拼接。

go
// ✅ 正确 - 结构化日志
log.Info("user login successful",
    "user_id", userID,
    "username", username,
    "login_ip", ip)

// ❌ 错误 - 字符串拼接
log.Info(fmt.Sprintf("user %s (ID: %d) login from %s", username, userID, ip))
typescript
// ✅ 正确 - 结构化日志
console.info('User login successful', {
  userId: user.id,
  username: user.username,
  loginIp: req.ip,
});

// ❌ 错误 - 字符串拼接
console.info(`User ${user.username} (ID: ${user.id}) login from ${req.ip}`);

日志消息规范

日志消息必须

  • 使用英文小写,单词间用空格分隔
  • 动词使用过去时(如 createdfailed)或进行时(如 creatingprocessing
  • 简洁明确,描述发生了什么
go
// ✅ 正确
log.Info("user created successfully")
log.Error("failed to connect database")
log.Warn("cache miss, fetching from database")

// ❌ 错误
log.Info("User Created Successfully")  // 不使用大写
log.Error("数据库连接失败")            // 不使用中文
log.Warn("CacheMiss")                  // 不使用驼峰

必需字段

通用字段

所有日志应该包含:

字段说明示例
timestamp时间戳(自动)2024-12-07T10:30:00Z
level日志级别(自动)INFO / ERROR
message日志消息user created successfully

上下文字段

根据场景添加:

字段使用场景示例
request_idHTTP 请求req_abc123
user_id用户操作12345
trace_id分布式追踪trace_xyz789
duration_ms性能监控150
error错误日志database connection timeout
stack错误堆栈goroutine 1 [running]...

HTTP 请求日志

请求日志

必须记录的字段:

go
// ✅ 正确 - 完整的请求日志
func LogRequest(c echo.Context) {
    start := time.Now()

    // 在中间件中记录请求开始
    log.Info("http request started",
        "request_id", c.Response().Header().Get(echo.HeaderXRequestID),
        "method", c.Request().Method,
        "path", c.Request().URL.Path,
        "query", c.Request().URL.RawQuery,
        "remote_ip", c.RealIP(),
        "user_agent", c.Request().UserAgent())

    // 处理请求...

    // 记录请求完成
    duration := time.Since(start).Milliseconds()
    log.Info("http request completed",
        "request_id", c.Response().Header().Get(echo.HeaderXRequestID),
        "method", c.Request().Method,
        "path", c.Request().URL.Path,
        "status", c.Response().Status,
        "duration_ms", duration)
}

// ❌ 错误 - 缺少关键字段
log.Info("request received", "path", c.Request().URL.Path)
typescript
// ✅ 正确 - 前端请求日志
async function apiRequest(url: string, options: RequestInit) {
  const requestId = generateRequestId();
  const startTime = Date.now();

  console.info('API request started', {
    requestId,
    method: options.method,
    url,
  });

  try {
    const response = await fetch(url, options);
    const duration = Date.now() - startTime;

    console.info('API request completed', {
      requestId,
      method: options.method,
      url,
      status: response.status,
      durationMs: duration,
    });

    return response;
  } catch (error) {
    const duration = Date.now() - startTime;

    console.error('API request failed', {
      requestId,
      method: options.method,
      url,
      error: error.message,
      durationMs: duration,
    });

    throw error;
  }
}

响应日志

禁止记录的内容:

  • ❌ 完整的响应体(可能包含大量数据)
  • ❌ 敏感的响应数据(密码、token)

可以记录:

  • ✅ 响应状态码
  • ✅ 响应大小
  • ✅ 处理时长

数据库操作日志

查询日志

go
// ✅ 正确 - 数据库操作日志
func GetUser(ctx context.Context, userID int64) (*User, error) {
    start := time.Now()

    var user User
    err := db.WithContext(ctx).Where("id = ?", userID).First(&user).Error

    duration := time.Since(start).Milliseconds()

    if err != nil {
        log.Error("database query failed",
            "operation", "get_user",
            "user_id", userID,
            "duration_ms", duration,
            "error", err.Error())
        return nil, err
    }

    log.Debug("database query completed",
        "operation", "get_user",
        "user_id", userID,
        "duration_ms", duration)

    return &user, nil
}

// ✅ 正确 - 慢查询日志
func LogSlowQuery(duration time.Duration, sql string) {
    if duration > time.Second {
        log.Warn("slow query detected",
            "duration_ms", duration.Milliseconds(),
            "sql", sql)
    }
}

事务日志

go
// ✅ 正确 - 事务日志
func CreateOrderWithItems(order *Order, items []*OrderItem) error {
    return db.Transaction(func(tx *gorm.DB) error {
        log.Info("transaction started", "operation", "create_order")

        if err := tx.Create(order).Error; err != nil {
            log.Error("failed to create order in transaction",
                "order_id", order.ID,
                "error", err.Error())
            return err
        }

        for _, item := range items {
            if err := tx.Create(item).Error; err != nil {
                log.Error("failed to create order item in transaction",
                    "order_id", order.ID,
                    "item_id", item.ID,
                    "error", err.Error())
                return err
            }
        }

        log.Info("transaction committed", "operation", "create_order", "order_id", order.ID)
        return nil
    })
}

敏感信息处理

禁止记录的敏感信息

绝对禁止记录以下信息:

  • ❌ 密码(明文或加密)
  • ❌ Token、API Key、密钥
  • ❌ 信用卡号、CVV
  • ❌ 身份证号、护照号
  • ❌ 完整的手机号、邮箱(可脱敏)
go
// ❌ 错误 - 记录敏感信息
log.Info("user login",
    "username", username,
    "password", password)  // 禁止

log.Info("payment processing",
    "card_number", cardNumber)  // 禁止

// ✅ 正确 - 不记录敏感字段
log.Info("user login",
    "username", username)

log.Info("payment processing",
    "card_last4", cardNumber[len(cardNumber)-4:])  // 只记录后4位
typescript
// ❌ 错误 - 记录敏感信息
console.info('User registration', {
  email: user.email,
  password: user.password,  // 禁止
});

// ✅ 正确 - 脱敏处理
console.info('User registration', {
  email: maskEmail(user.email),  // user@example.com → u***@example.com
});

function maskEmail(email: string): string {
  const [local, domain] = email.split('@');
  return `${local[0]}***@${domain}`;
}

数据脱敏规则

手机号

go
// ✅ 正确 - 手机号脱敏
func MaskPhone(phone string) string {
    if len(phone) != 11 {
        return "***"
    }
    return phone[:3] + "****" + phone[7:]  // 138****5678
}

log.Info("sms sent", "phone", MaskPhone(phone))

邮箱

go
// ✅ 正确 - 邮箱脱敏
func MaskEmail(email string) string {
    parts := strings.Split(email, "@")
    if len(parts) != 2 {
        return "***"
    }
    local := parts[0]
    if len(local) > 2 {
        return local[:1] + "***@" + parts[1]  // u***@example.com
    }
    return "***@" + parts[1]
}

log.Info("email sent", "to", MaskEmail(email))

错误日志规范

错误上下文

必须包含的信息:

go
// ✅ 正确 - 完整的错误上下文
func UpdateUser(ctx context.Context, userID int64, req *UpdateUserRequest) error {
    requestID := ctx.Value("request_id").(string)

    var user User
    if err := db.First(&user, userID).Error; err != nil {
        log.Error("failed to find user",
            "request_id", requestID,
            "user_id", userID,
            "operation", "update_user",
            "error", err.Error())
        return fmt.Errorf("查询用户失败: %w", err)
    }

    user.Username = req.Username
    if err := db.Save(&user).Error; err != nil {
        log.Error("failed to save user",
            "request_id", requestID,
            "user_id", userID,
            "operation", "update_user",
            "error", err.Error(),
            "stack", string(debug.Stack()))
        return fmt.Errorf("更新用户失败: %w", err)
    }

    return nil
}

错误堆栈

后端错误日志必须包含堆栈跟踪

go
// ✅ 正确 - 包含堆栈
import "runtime/debug"

log.Error("unexpected error",
    "error", err.Error(),
    "stack", string(debug.Stack()))

// ❌ 错误 - 缺少堆栈
log.Error("unexpected error", "error", err.Error())

性能日志

操作耗时

应该记录关键操作的耗时:

go
// ✅ 正确 - 记录耗时
func ExpensiveOperation() error {
    start := time.Now()
    defer func() {
        duration := time.Since(start).Milliseconds()
        log.Info("expensive operation completed",
            "duration_ms", duration)

        if duration > 1000 {
            log.Warn("slow operation detected",
                "operation", "expensive_operation",
                "duration_ms", duration)
        }
    }()

    // 执行操作...
    return nil
}
typescript
// ✅ 正确 - 记录页面加载时间
function measurePageLoad() {
  window.addEventListener('load', () => {
    const perfData = performance.timing;
    const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;

    console.info('Page load completed', {
      durationMs: pageLoadTime,
      domReady: perfData.domContentLoadedEventEnd - perfData.navigationStart,
      resourceLoad: perfData.loadEventEnd - perfData.domContentLoadedEventEnd,
    });
  });
}

日志频率控制

避免过度记录

go
// ❌ 错误 - 循环内记录
for _, item := range items {
    log.Info("processing item", "item_id", item.ID)  // 避免
}

// ✅ 正确 - 批量记录
log.Info("processing items",
    "total_count", len(items),
    "first_id", items[0].ID,
    "last_id", items[len(items)-1].ID)

// 处理每个 item...

log.Info("items processed",
    "total_count", len(items),
    "success_count", successCount,
    "failed_count", failedCount)

限流日志

go
// ✅ 正确 - 使用限流避免日志洪水
import "time"

var lastLogTime time.Time
var logThrottle = time.Second * 10

func ThrottledLog(message string) {
    now := time.Now()
    if now.Sub(lastLogTime) > logThrottle {
        log.Warn(message)
        lastLogTime = now
    }
}

// 使用
for {
    if err := checkHealth(); err != nil {
        ThrottledLog("health check failed")  // 最多每10秒记录一次
    }
}

前端日志最佳实践

生产环境日志

typescript
// ✅ 正确 - 根据环境控制日志级别
const isDevelopment = import.meta.env.MODE === 'development';

export const logger = {
  debug: (...args: any[]) => {
    if (isDevelopment) {
      console.debug(...args);
    }
  },

  info: (...args: any[]) => {
    console.info(...args);
  },

  warn: (...args: any[]) => {
    console.warn(...args);
  },

  error: (...args: any[]) => {
    console.error(...args);
    // 生产环境发送到错误追踪服务
    if (!isDevelopment) {
      sendToErrorTracking(...args);
    }
  },
};

// 使用
logger.debug('Component mounted', { props });
logger.error('API call failed', { error, url });

用户行为日志

typescript
// ✅ 正确 - 记录关键用户行为
function trackUserAction(action: string, metadata?: Record<string, any>) {
  console.info('User action', {
    action,
    timestamp: Date.now(),
    userId: getCurrentUserId(),
    ...metadata,
  });

  // 发送到分析服务
  analytics.track(action, metadata);
}

// 使用
trackUserAction('button_click', { buttonId: 'submit-order' });
trackUserAction('page_view', { path: '/checkout' });

相关文档