Files
lijiaoqiao/supply-api/docs/logging_standardization_plan_v1.md
Your Name 789707e4f3 feat(logging): 添加 slog-based 结构化日志支持
1. 添加 slog_logger.go 实现基于 Go 1.21+ slog 的结构化日志
2. 支持 trace_id、request_id、tenant_id 等标准字段注入
3. 添加日志标准化重构方案文档

推荐使用 Go 内置 log/slog,无需第三方依赖。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 07:41:37 +08:00

4.0 KiB
Raw Blame History

日志标准化重构方案

问题分析

当前状态

  • 已实现结构化日志接口 (internal/pkg/logging/logger.go)
  • 支持 JSON 格式输出,带 trace_id、span_id、request_id 等标准字段
  • 内置敏感字段脱敏

问题

大量代码仍使用标准库 log

log.Printf("[AUDIT_ERROR] failed to emit audit event: %v", err)

缺陷:

  1. 非结构化,难以解析和搜索
  2. 缺少 trace_id无法关联请求链路
  3. 缺少标准化字段grep 难度大
  4. 无法输出到统一日志收集系统

优化方案

1. 创建 log.Logger 适配器

将标准库 log 重定向到结构化日志:

// logadapter 包:将标准 log 重定向到结构化日志
package logadapter

import (
    "log"
    "os"
    "sync"
)

var (
    defaultLogger Logger
    mu            sync.Mutex
)

// SetLogger 设置全局日志实例
func SetLogger(l Logger) {
    mu.Lock()
    defer mu.Unlock()
    defaultLogger = l
}

// Printf 实现 log.Logger 接口
func (l *stdLoggerAdapter) Printf(format string, v ...interface{}) {
    if defaultLogger != nil {
        defaultLogger.Info(fmt.Sprintf(format, v...), nil)
    }
}

// init 拦截标准库 log
func init() {
    log.SetOutput(&stdLoggerAdapter{})
    log.SetFlags(0) // 禁用标准库时间戳
}

2. 使用 slog 作为后端(推荐)

Go 1.21+ 内置 log/slog,直接使用:

import "log/slog"

func main() {
    // 初始化 slog JSON handler
    handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelInfo,
    })
    slog.SetDefault(slog.New(handler))

    // 使用 slog
    slog.Info("server started", "addr", ":8080")
    slog.Error("connection failed", "err", err, "peer", "10.0.0.1")
}

3. 统一字段命名

字段 标准化名称 说明
租户ID tenant_id
用户ID user_id
请求ID request_id HTTP 请求追踪
Trace ID trace_id W3C Trace Context
操作类型 operation withdraw, create
耗时 duration_ms 毫秒
状态码 status_code HTTP 状态码
错误码 error_code 业务错误码

4. 实施步骤

Phase 1: 基础设施

  1. 使用 log/slog 替代自定义实现
  2. 配置 JSON handler 输出到 stdout
  3. 设置默认字段service_name, version

Phase 2: HTTP 层改造

  1. 中间件注入 trace_id、request_id
  2. Request/Response 日志
  3. 慢请求日志

Phase 3: Domain 层改造

  1. 业务操作日志withdraw, create
  2. 错误日志包含 error_code
  3. 审计事件日志

Phase 4: 清理

  1. 移除所有 log.Printf
  2. 添加 log level 配置
  3. 支持日志采样

5. 日志格式规范

JSON 日志条目:

{
  "time": "2026-04-13T10:15:30.123Z",
  "level": "INFO",
  "service": "supply-api",
  "trace_id": "abc123",
  "request_id": "req-456",
  "tenant_id": 1001,
  "operation": "withdraw",
  "duration_ms": 45,
  "status_code": 200,
  "message": "withdraw completed"
}

错误日志:

{
  "time": "2026-04-13T10:15:30.123Z",
  "level": "ERROR",
  "service": "supply-api",
  "error_code": "SUP_SET_4001",
  "error": "withdraw amount exceeds available balance",
  "tenant_id": 1001,
  "operation": "withdraw",
  "stack_trace": "..."
}

6. 迁移检查清单

  • 移除 log.Printf 替换为结构化日志
  • 所有日志包含 trace_id(从 context 提取)
  • 错误日志包含 error_code
  • 敏感字段自动脱敏
  • 配置 log level 支持动态调整
  • Benchmark 测试日志性能影响 < 5%

推荐方案

使用 Go 1.21+ 内置 log/slog,无需引入第三方依赖。

// 推荐配置
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level:     slog.LevelInfo,
    AddSource: false,
    ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
        if a.Key == slog.TimeKey {
            a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339Nano))
        }
        return a
    },
})
slog.SetDefault(slog.New(handler))