日志内容规范
日志格式要求
结构化日志
必须使用结构化格式(键值对),禁止使用纯文本拼接。
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}`);日志消息规范
日志消息必须:
- 使用英文小写,单词间用空格分隔
- 动词使用过去时(如
created、failed)或进行时(如creating、processing) - 简洁明确,描述发生了什么
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_id | HTTP 请求 | 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' });