logger

package
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 8, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

日志模块设计文档

版本: v1.0
更新时间: 2025-12-10
适用范围: Go 微服务项目

1. 设计目标

1.1 解决的问题
问题 描述
日志碎片化 一个请求经过多个服务层,日志分散难以串联
缺乏上下文 日志不包含 trace_id,无法追踪完整请求链路
格式不统一 字段命名混乱,如 user_id vs userId vs uid
日志不规范 使用 printf 格式而非结构化 key-value
层级不清晰 不同层级的日志混在一起,难以分析
1.2 设计原则
  1. 上下文传递: 通过 context.Context 传递 Logger,确保追踪信息不丢失
  2. 结构化日志: 所有日志必须是 key-value 结构,便于机器解析
  3. 字段标准化: 统一字段命名,提供常量定义
  4. 分层日志: 不同层级(HTTP/gRPC/Service/DB)有明确的日志策略
  5. 零侵入性: 业务代码只需调用 logger.L(ctx),无需关心底层实现

2. 整体架构

┌─────────────────────────────────────────────────────────────────────────┐
│                            请求生命周期                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                      1. 边界层 (Boundary)                        │   │
│  │  ┌──────────────────┐      ┌──────────────────┐                 │   │
│  │  │   HTTP 中间件     │      │   gRPC 拦截器     │                 │   │
│  │  │  (api_logger)    │      │ (grpc_server_    │                 │   │
│  │  │                  │      │     logger)      │                 │   │
│  │  └────────┬─────────┘      └────────┬─────────┘                 │   │
│  │           │                         │                           │   │
│  │           └────────────┬────────────┘                           │   │
│  │                        │                                        │   │
│  │                        ▼                                        │   │
│  │           ┌──────────────────────────┐                          │   │
│  │           │  创建 RequestLogger      │                          │   │
│  │           │  注入到 context          │                          │   │
│  │           │  记录 Access Log (开始)  │                          │   │
│  │           └────────────┬─────────────┘                          │   │
│  └────────────────────────┼─────────────────────────────────────────┘   │
│                           │                                             │
│  ┌────────────────────────┼─────────────────────────────────────────┐   │
│  │                        ▼         2. 应用层 (Application)         │   │
│  │           ┌──────────────────────────┐                          │   │
│  │           │  l := logger.L(ctx)      │  ← 获取请求范围 Logger    │   │
│  │           │  l.Infow("操作开始",     │                          │   │
│  │           │    "action", "create",   │                          │   │
│  │           │    "resource", "user",   │                          │   │
│  │           │  )                       │                          │   │
│  │           └────────────┬─────────────┘                          │   │
│  └────────────────────────┼─────────────────────────────────────────┘   │
│                           │                                             │
│  ┌────────────────────────┼─────────────────────────────────────────┐   │
│  │                        ▼         3. 基础设施层 (Infrastructure)  │   │
│  │  ┌──────────────────┐  ┌──────────────────┐                     │   │
│  │  │   GORM Logger    │  │   Redis Hook     │                     │   │
│  │  │  (自动从 ctx     │  │  (自动从 ctx     │                     │   │
│  │  │   提取 trace)    │  │   提取 trace)    │                     │   │
│  │  └──────────────────┘  └──────────────────┘                     │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                           │                                             │
│  ┌────────────────────────┼─────────────────────────────────────────┐   │
│  │                        ▼         4. 边界层 (响应)                │   │
│  │           ┌──────────────────────────┐                          │   │
│  │           │  记录 Access Log (结束)  │                          │   │
│  │           │  - status_code           │                          │   │
│  │           │  - duration_ms           │                          │   │
│  │           │  - response_body         │                          │   │
│  │           └──────────────────────────┘                          │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

3. 核心组件

3.1 请求范围 Logger (logger/context.go)

核心思想: 每个请求有独立的 Logger 实例,预设了追踪字段(trace_id, request_id 等)。

3.1.1 有 Context 场景(推荐)

适用于请求链路中的日志记录,自动包含追踪信息。

// L 从 context 获取 Logger(核心 API)
// 如果 context 中没有 Logger,返回带追踪信息的默认 Logger
func L(ctx context.Context) *RequestLogger

// 使用示例
func HandleRequest(ctx context.Context) {
    logger.L(ctx).Info("Processing request", 
        log.String("action", "create"),
        log.String("resource", "user"),
    )
}
3.1.2 无 Context 场景

适用于程序启动、后台任务、定时任务等非请求链路场景。

方法 1: Default() - 最简单的方式
// Default 返回默认的 RequestLogger(无上下文场景使用)
func Default() *RequestLogger

// 使用示例
func main() {
    // 程序启动日志
    logger.Default().Info("Application started", 
        log.String("version", "1.0.0"),
        log.String("env", "production"),
    )
    
    // 链式调用
    appLogger := logger.Default().
        WithField("app", "user-service").
        WithField("instance", "server-01")
    
    appLogger.Info("Service initialized")
}
方法 2: New(fields...) - 预设字段
// New 创建一个带有自定义字段的 RequestLogger
func New(fields ...log.Field) *RequestLogger

// 使用示例
func StartBackgroundTask() {
    // 后台任务日志
    taskLogger := logger.New(
        log.String("task", "data_sync"),
        log.String("module", "background"),
    )
    
    taskLogger.Info("Task started")
    taskLogger.Info("Processing", log.Int("records", 1000))
    taskLogger.Info("Task completed")
}

// 模块级别的 Logger(可作为全局变量)
var dbLogger = logger.New(log.String("component", "database"))

func InitDatabase() {
    dbLogger.Info("Connection pool initialized", 
        log.Int("max_connections", 100),
    )
}
3.1.3 使用场景对比
场景 推荐方法 示例
HTTP/gRPC 请求处理 logger.L(ctx) 请求链路中的业务逻辑
程序启动 logger.Default() main 函数初始化
后台任务/定时任务 logger.New(...) 独立的后台处理流程
独立 goroutine logger.New(...) 无法传递 context 的异步任务
模块/组件日志 logger.New(...) 全局 Logger 变量
错误恢复 logger.Default() panic recover 等场景
3.1.4 完整示例
package logger

import (
    "context"
    "github.com/FangcunMount/component-base/pkg/log"
    "go.uber.org/zap/zapcore"
)

// ctxLoggerKey 用于在 context 中存储 Logger
type ctxLoggerKey struct{}

// RequestLogger 请求范围的日志记录器
type RequestLogger struct {
    fields []log.Field  // 预设的追踪字段
}

// NewRequestLogger 创建请求范围的 Logger
// 自动从 context 提取 trace_id, span_id, request_id
func NewRequestLogger(ctx context.Context, fields ...log.Field) *RequestLogger {
    // 从 context 获取追踪字段
    baseFields := log.TraceFields(ctx)
    
    // 合并自定义字段
    allFields := make([]log.Field, 0, len(baseFields)+len(fields))
    allFields = append(allFields, baseFields...)
    allFields = append(allFields, fields...)
    
    return &RequestLogger{fields: allFields}
}

// WithLogger 将 Logger 放入 context
func WithLogger(ctx context.Context, logger *RequestLogger) context.Context {
    return context.WithValue(ctx, ctxLoggerKey{}, logger)
}

// L 从 context 获取 Logger(有 context 场景)
func L(ctx context.Context) *RequestLogger {
    if logger, ok := ctx.Value(ctxLoggerKey{}).(*RequestLogger); ok {
        return logger
    }
    return NewRequestLogger(ctx)
}

// Default 返回默认 Logger(无 context 场景)
func Default() *RequestLogger {
    return &RequestLogger{fields: []log.Field{}}
}

// New 创建带预设字段的 Logger(无 context 场景)
func New(fields ...log.Field) *RequestLogger {
    return &RequestLogger{fields: fields}
}

// WithFields 创建带额外字段的新 Logger(不可变设计)
func (l *RequestLogger) WithFields(fields ...log.Field) *RequestLogger {
    newFields := make([]log.Field, 0, len(l.fields)+len(fields))
    newFields = append(newFields, l.fields...)
    newFields = append(newFields, fields...)
    return &RequestLogger{fields: newFields}
}

// Infow 记录 Info 级别日志(key-value 格式)
func (l *RequestLogger) Infow(msg string, keysAndValues ...interface{}) {
    kvs := l.prependFields(keysAndValues)
    log.Infow(msg, kvs...)
}

// Warnw 记录 Warn 级别日志
func (l *RequestLogger) Warnw(msg string, keysAndValues ...interface{}) {
    kvs := l.prependFields(keysAndValues)
    log.Warnw(msg, kvs...)
}

// Errorw 记录 Error 级别日志
func (l *RequestLogger) Errorw(msg string, keysAndValues ...interface{}) {
    kvs := l.prependFields(keysAndValues)
    log.Errorw(msg, kvs...)
}

// Debugw 记录 Debug 级别日志
func (l *RequestLogger) Debugw(msg string, keysAndValues ...interface{}) {
    kvs := l.prependFields(keysAndValues)
    log.Debugw(msg, kvs...)
}

// prependFields 将预设字段转换为 kv 并前置
func (l *RequestLogger) prependFields(keysAndValues []interface{}) []interface{} {
    baseKV := fieldsToKV(l.fields)
    result := make([]interface{}, 0, len(baseKV)+len(keysAndValues))
    result = append(result, baseKV...)
    result = append(result, keysAndValues...)
    return result
}

// fieldsToKV 将 log.Field 转换为 key-value 切片
func fieldsToKV(fields []log.Field) []interface{} {
    if len(fields) == 0 {
        return nil
    }
    kv := make([]interface{}, 0, len(fields)*2)
    for _, f := range fields {
        kv = append(kv, f.Key, fieldValue(f))
    }
    return kv
}

// fieldValue 从 zapcore.Field 提取值
func fieldValue(f log.Field) interface{} {
    switch f.Type {
    case zapcore.StringType:
        return f.String
    case zapcore.Int64Type, zapcore.Int32Type:
        return f.Integer
    case zapcore.BoolType:
        return f.Integer == 1
    default:
        if f.Interface != nil {
            return f.Interface
        }
        return f.String
    }
}
3.2 标准字段定义 (logger/fields.go)

核心思想: 统一字段命名,避免同一概念使用不同名称。

package logger

import "github.com/FangcunMount/component-base/pkg/log"

// ============================================================================
// 标准字段名称常量
// ============================================================================

// 追踪相关
const (
    FieldTraceID   = "trace_id"
    FieldSpanID    = "span_id"
    FieldRequestID = "request_id"
)

// 身份相关
const (
    FieldUserID    = "user_id"
    FieldAccountID = "account_id"
    FieldTenantID  = "tenant_id"
    FieldClientIP  = "client_ip"
)

// 操作相关
const (
    FieldAction     = "action"      // 动作类型
    FieldResource   = "resource"    // 资源类型
    FieldResourceID = "resource_id" // 资源 ID
    FieldResult     = "result"      // 操作结果
)

// 性能相关
const (
    FieldDurationMS = "duration_ms"
    FieldLatency    = "latency"
)

// 错误相关
const (
    FieldError     = "error"
    FieldErrorCode = "error_code"
)

// HTTP 相关
const (
    FieldMethod     = "method"
    FieldPath       = "path"
    FieldStatusCode = "status_code"
)

// gRPC 相关
const (
    FieldGRPCMethod  = "grpc.method"
    FieldGRPCService = "grpc.service"
    FieldGRPCCode    = "grpc.code"
)

// ============================================================================
// 标准操作类型(用于审计日志)
// ============================================================================

const (
    ActionCreate   = "create"
    ActionRead     = "read"
    ActionUpdate   = "update"
    ActionDelete   = "delete"
    ActionList     = "list"
    ActionLogin    = "login"
    ActionLogout   = "logout"
    ActionRegister = "register"
)

// ============================================================================
// 标准资源类型
// ============================================================================

const (
    ResourceUser         = "user"
    ResourceChild        = "child"
    ResourceGuardianship = "guardianship"
    ResourceCredential   = "credential"
    ResourceToken        = "token"
)

// ============================================================================
// 标准结果类型
// ============================================================================

const (
    ResultSuccess = "success"
    ResultFailed  = "failed"
    ResultDenied  = "denied"
)

// ============================================================================
// 辅助函数
// ============================================================================

// OperationFields 生成操作相关字段
func OperationFields(action, resource, resourceID string) []log.Field {
    fields := []log.Field{
        log.String(FieldAction, action),
        log.String(FieldResource, resource),
    }
    if resourceID != "" {
        fields = append(fields, log.String(FieldResourceID, resourceID))
    }
    return fields
}

// UserFields 生成用户相关字段
func UserFields(userID, accountID, tenantID string) []log.Field {
    fields := make([]log.Field, 0, 3)
    if userID != "" {
        fields = append(fields, log.String(FieldUserID, userID))
    }
    if accountID != "" {
        fields = append(fields, log.String(FieldAccountID, accountID))
    }
    if tenantID != "" {
        fields = append(fields, log.String(FieldTenantID, tenantID))
    }
    return fields
}

// ErrorFields 生成错误相关字段
func ErrorFields(err error, code int) []log.Field {
    fields := []log.Field{
        log.String(FieldError, err.Error()),
    }
    if code != 0 {
        fields = append(fields, log.Int(FieldErrorCode, code))
    }
    return fields
}
3.3 HTTP 日志中间件 (middleware/api_logger.go)

核心功能:

  1. 记录请求开始/结束的 Access Log
  2. 创建 RequestLogger 并注入 context
  3. 捕获请求体/响应体
  4. 敏感数据脱敏
package middleware

import (
    "bytes"
    "io"
    "net/http"
    "time"

    "github.com/FangcunMount/component-base/pkg/log"
    "github.com/FangcunMount/iam-contracts/internal/pkg/logger"
    "github.com/gin-gonic/gin"
)

// APILoggerConfig 配置项
type APILoggerConfig struct {
    SkipPaths          []string  // 跳过日志的路径
    LogRequestBody     bool      // 是否记录请求体
    LogResponseBody    bool      // 是否记录响应体
    MaskSensitiveData  bool      // 是否脱敏
    MaxBodyBytes       int64     // 最大记录的 body 大小
}

// APILogger 返回 API 日志中间件
func APILogger() gin.HandlerFunc {
    return APILoggerWithConfig(DefaultAPILoggerConfig())
}

// APILoggerWithConfig 带配置的中间件
func APILoggerWithConfig(config APILoggerConfig) gin.HandlerFunc {
    skipPaths := buildSkipMap(config.SkipPaths)

    return func(c *gin.Context) {
        // 跳过指定路径
        if _, ok := skipPaths[c.Request.URL.Path]; ok {
            c.Next()
            return
        }

        start := time.Now()
        requestID := c.GetString("X-Request-ID")

        // === 核心:创建请求范围 Logger 并注入 context ===
        reqLogger := logger.NewRequestLogger(c.Request.Context(),
            log.String(logger.FieldMethod, c.Request.Method),
            log.String(logger.FieldPath, c.Request.URL.Path),
            log.String(logger.FieldClientIP, c.ClientIP()),
            log.String(logger.FieldRequestID, requestID),
        )
        ctx := logger.WithLogger(c.Request.Context(), reqLogger)
        c.Request = c.Request.WithContext(ctx)

        // 记录请求开始
        logRequestStart(c, config, requestID)

        // 读取请求体(如果需要)
        var requestBody []byte
        if config.LogRequestBody && c.Request.Body != nil {
            requestBody = readAndRestoreRequestBody(c, config.MaxBodyBytes)
        }

        // 包装 ResponseWriter 捕获响应
        writer := newBodyCaptureWriter(c.Writer, config.LogResponseBody, config.MaxBodyBytes)
        c.Writer = writer

        // 处理请求
        c.Next()

        // 记录请求结束
        latency := time.Since(start)
        logRequestEnd(c, config, requestID, latency, writer.Status(), requestBody, writer.Body())
    }
}

func logRequestStart(c *gin.Context, config APILoggerConfig, requestID string) {
    fields := []log.Field{
        log.String("event", "request_start"),
        log.String("request_id", requestID),
        log.String("method", c.Request.Method),
        log.String("path", c.Request.URL.Path),
        log.String("client_ip", c.ClientIP()),
    }
    
    // 添加追踪字段
    fields = append(fields, log.TraceFields(c.Request.Context())...)
    
    log.HTTP("HTTP Request Started", fields...)
}

func logRequestEnd(c *gin.Context, config APILoggerConfig, requestID string, 
    latency time.Duration, statusCode int, requestBody, responseBody []byte) {
    
    fields := []log.Field{
        log.String("event", "request_end"),
        log.String("request_id", requestID),
        log.Int("status_code", statusCode),
        log.Int64("duration_ms", latency.Milliseconds()),
    }
    
    // 根据状态码选择日志级别
    if statusCode >= http.StatusInternalServerError {
        log.HTTPError("HTTP Request Failed", fields...)
    } else if statusCode >= http.StatusBadRequest {
        log.HTTPWarn("HTTP Request Client Error", fields...)
    } else {
        log.HTTPDebug("HTTP Request Completed", fields...)
    }
}
3.4 gRPC 服务端日志拦截器 (middleware/grpc_server_logger.go)

核心功能:

  1. 提取或生成追踪 ID
  2. 创建 RequestLogger 并注入 context
  3. 记录请求/响应载荷
  4. 记录执行耗时和错误
package middleware

import (
    "context"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/status"

    "github.com/FangcunMount/component-base/pkg/log"
    "github.com/FangcunMount/iam-contracts/internal/pkg/logger"
)

// UnaryServerLoggingInterceptor gRPC 一元服务端日志拦截器
func UnaryServerLoggingInterceptor() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, 
        handler grpc.UnaryHandler) (interface{}, error) {
        
        start := time.Now()

        // 提取或生成追踪 ID
        traceID, spanID, requestID := extractOrGenerateTraceIDs(ctx)
        ctx = log.WithTraceContext(ctx, traceID, spanID, requestID)

        // 提取服务名和方法名
        service, method := splitMethodName(info.FullMethod)

        // === 核心:创建请求范围 Logger 并注入 context ===
        reqLogger := logger.NewRequestLogger(ctx,
            log.String(logger.FieldGRPCService, service),
            log.String(logger.FieldGRPCMethod, method),
            log.String(logger.FieldClientIP, extractClientIP(ctx)),
        )
        ctx = logger.WithLogger(ctx, reqLogger)

        // 记录请求开始
        reqLogger.Infow("gRPC request started",
            "event", "request_start",
            "grpc.full_method", info.FullMethod,
        )

        // 执行处理
        resp, err := handler(ctx, req)

        // 计算耗时
        latency := time.Since(start)

        // 记录请求结束
        if err != nil {
            st := status.Convert(err)
            reqLogger.Errorw("gRPC request failed",
                "event", "request_end",
                "grpc.code", st.Code().String(),
                "error", st.Message(),
                "duration_ms", latency.Milliseconds(),
                "result", logger.ResultFailed,
            )
        } else {
            reqLogger.Infow("gRPC request completed",
                "event", "request_end",
                "grpc.code", "OK",
                "duration_ms", latency.Milliseconds(),
                "result", logger.ResultSuccess,
            )
        }

        return resp, err
    }
}

4. 使用指南

4.1 在 Application Service 中使用
package user

import (
    "context"
    "github.com/your-project/internal/pkg/logger"
)

type userService struct {
    repo UserRepository
}

func (s *userService) CreateUser(ctx context.Context, dto CreateUserDTO) (*User, error) {
    // 1. 获取请求范围的 Logger
    l := logger.L(ctx)
    
    // 2. 记录操作开始(Debug 级别,生产环境可关闭)
    l.Debugw("开始创建用户",
        "action", logger.ActionCreate,
        "resource", logger.ResourceUser,
        "username", dto.Username,
    )
    
    // 3. 执行业务逻辑
    user, err := s.repo.Create(ctx, dto)
    if err != nil {
        // 4. 记录错误(Warn 或 Error 级别)
        l.Errorw("创建用户失败",
            "action", logger.ActionCreate,
            "resource", logger.ResourceUser,
            "error", err.Error(),
            "result", logger.ResultFailed,
        )
        return nil, err
    }
    
    // 5. 记录成功(Info 级别)
    l.Infow("用户创建成功",
        "action", logger.ActionCreate,
        "resource", logger.ResourceUser,
        "resource_id", user.ID,
        "result", logger.ResultSuccess,
    )
    
    return user, nil
}
4.2 在 Handler 中使用
package handler

import (
    "github.com/gin-gonic/gin"
    "github.com/your-project/internal/pkg/logger"
)

func (h *UserHandler) CreateUser(c *gin.Context) {
    // Logger 已由中间件注入,直接获取
    l := logger.L(c.Request.Context())
    
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        l.Warnw("请求参数解析失败",
            "error", err.Error(),
        )
        h.Error(c, err)
        return
    }
    
    // 调用 Service(Logger 会通过 context 传递)
    user, err := h.userService.CreateUser(c.Request.Context(), req.ToDTO())
    if err != nil {
        h.Error(c, err)
        return
    }
    
    h.Success(c, user)
}
4.3 添加自定义字段
func (s *orderService) ProcessOrder(ctx context.Context, orderID string) error {
    // 获取 Logger 并添加订单相关字段
    l := logger.L(ctx).WithFields(
        log.String("order_id", orderID),
        log.String("resource", "order"),
    )
    
    l.Infow("开始处理订单")
    
    // 后续日志都会自动带上 order_id
    l.Debugw("验证订单状态")
    l.Infow("订单处理完成")
    
    return nil
}

5. 日志级别使用规范

级别 使用场景 生产环境
Debug 详细的调试信息、变量值、SQL 语句 关闭
Info 重要业务操作的开始/结束、状态变化 开启
Warn 可恢复的错误、参数校验失败、权限拒绝 开启
Error 不可恢复的错误、异常情况 开启
5.1 日志级别选择指南
// ❌ 错误示例
log.Info("SQL: SELECT * FROM users")           // 应该用 Debug
log.Error("user not found")                     // 业务错误应该用 Warn
log.Debug("用户登录成功")                        // 重要操作应该用 Info

// ✅ 正确示例
l.Debugw("执行 SQL 查询", "sql", "SELECT * FROM users")
l.Warnw("用户不存在", "user_id", userID, "result", logger.ResultFailed)
l.Infow("用户登录成功", "user_id", userID, "result", logger.ResultSuccess)

6. 日志输出示例

6.1 完整请求链路日志
// 1. HTTP 请求进入
{
    "level": "info",
    "ts": "2025-12-10T10:00:00.000Z",
    "msg": "HTTP Request Started",
    "event": "request_start",
    "trace_id": "abc123def456",
    "request_id": "req-001",
    "method": "POST",
    "path": "/v1/users",
    "client_ip": "192.168.1.100"
}

// 2. 应用层日志
{
    "level": "debug",
    "ts": "2025-12-10T10:00:00.050Z",
    "msg": "开始创建用户",
    "trace_id": "abc123def456",
    "request_id": "req-001",
    "action": "create",
    "resource": "user",
    "username": "john_doe"
}

// 3. 数据库操作日志
{
    "level": "debug",
    "ts": "2025-12-10T10:00:00.100Z",
    "msg": "GORM trace",
    "trace_id": "abc123def456",
    "request_id": "req-001",
    "sql": "INSERT INTO users (username, email) VALUES (?, ?)",
    "elapsed_ms": 15.5,
    "rows": 1
}

// 4. 应用层成功日志
{
    "level": "info",
    "ts": "2025-12-10T10:00:00.120Z",
    "msg": "用户创建成功",
    "trace_id": "abc123def456",
    "request_id": "req-001",
    "action": "create",
    "resource": "user",
    "resource_id": "12345",
    "result": "success"
}

// 5. HTTP 请求结束
{
    "level": "info",
    "ts": "2025-12-10T10:00:00.150Z",
    "msg": "HTTP Request Completed",
    "event": "request_end",
    "trace_id": "abc123def456",
    "request_id": "req-001",
    "status_code": 201,
    "duration_ms": 150
}

通过 trace_id 可以轻松串联整个请求的所有日志。

7. 集成到新项目

7.1 目录结构
internal/
└── pkg/
    ├── logger/
    │   ├── context.go    # RequestLogger 核心实现
    │   ├── fields.go     # 标准字段定义
    │   ├── gorm.go       # GORM 日志适配器
    │   ├── redis.go      # Redis 日志钩子
    │   ├── mongo.go      # MongoDB 日志钩子
    │   ├── grpc.go       # gRPC 日志辅助
    │   ├── http.go       # HTTP 日志辅助
    │   └── doc.go        # 包文档
    └── middleware/
        ├── api_logger.go          # HTTP 日志中间件
        ├── grpc_server_logger.go  # gRPC 服务端日志拦截器
        └── grpc_logger.go         # gRPC 客户端日志拦截器
7.2 数据库日志集成
7.2.1 GORM 日志集成
import (
    "github.com/FangcunMount/component-base/pkg/logger"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

// 创建 GORM Logger
gormLogger := logger.NewGormLoggerWithConfig(logger.GormConfig{
    SlowThreshold: 200 * time.Millisecond,  // 慢查询阈值
    LogLevel:      logger.GormInfo,          // 日志级别
    Colorful:      false,                    // 是否彩色输出
})

// 初始化数据库连接
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    Logger: gormLogger,
})
7.2.2 Redis 日志集成
import (
    "github.com/FangcunMount/component-base/pkg/logger"
    "github.com/redis/go-redis/v9"
)

// 创建 Redis Hook
redisHook := logger.NewRedisHookWithConfig(logger.RedisHookConfig{
    Enabled:       true,
    SlowThreshold: 200 * time.Millisecond,  // 慢命令阈值
})

// 初始化 Redis 客户端
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})
rdb.AddHook(redisHook)
7.2.3 MongoDB 日志集成
import (
    "github.com/FangcunMount/component-base/pkg/logger"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

// 创建 MongoDB Hook
mongoHook := logger.NewMongoHookWithConfig(logger.MongoHookConfig{
    Enabled:       true,
    SlowThreshold: 200 * time.Millisecond,  // 慢查询阈值
})

// 配置 MongoDB 客户端
clientOptions := options.Client().
    ApplyURI("mongodb://localhost:27017").
    SetMonitor(mongoHook.CommandMonitor()).      // 命令监控
    SetPoolMonitor(mongoHook.PoolMonitor()).     // 连接池监控
    SetServerMonitor(mongoHook.ServerMonitor())  // 服务器监控

// 连接到 MongoDB
client, err := mongo.Connect(ctx, clientOptions)

日志示例:

{
  "level": "debug",
  "ts": "2025-12-11T10:30:45.123+08:00",
  "type": "MongoDB",
  "msg": "MongoDB command succeeded",
  "request_id": 12345,
  "database": "mydb",
  "command": "find",
  "connection_id": "localhost:27017[1]",
  "elapsed_ms": 15.234,
  "trace_id": "abc123",
  "span_id": "def456"
}

慢查询日志:

{
  "level": "warn",
  "ts": "2025-12-11T10:30:45.456+08:00",
  "type": "MongoDB",
  "msg": "MongoDB slow command",
  "request_id": 12346,
  "database": "mydb",
  "command": "aggregate",
  "connection_id": "localhost:27017[1]",
  "elapsed_ms": 350.678,
  "event": "slow_command",
  "slow_threshold": "200ms",
  "trace_id": "abc124",
  "span_id": "def457"
}
7.3 依赖项
// go.mod
require (
    go.uber.org/zap v1.27.0
    github.com/gin-gonic/gin v1.10.0
    google.golang.org/grpc v1.68.0
    gorm.io/gorm v1.25.0
    github.com/redis/go-redis/v9 v9.0.0
    go.mongodb.org/mongo-driver v1.12.0
)
7.4 初始化步骤
  1. 复制 logger 包 到你的项目

  2. 复制中间件 到你的项目

  3. 配置 HTTP 服务器:

    // 在 Gin 引擎中添加中间件
    engine.Use(middleware.Tracing())    // 生成 trace_id
    engine.Use(middleware.APILogger())  // 日志中间件
    
  4. 配置 gRPC 服务器:

    // 在 gRPC 服务器选项中添加拦截器
    grpc.ChainUnaryInterceptor(
        middleware.RecoveryInterceptor(),
        middleware.UnaryServerLoggingInterceptor(),
    )
    

8. 最佳实践

8.1 DO ✅
  • 使用 logger.L(ctx) 获取 Logger
  • 使用标准字段常量(如 logger.FieldUserID
  • 使用结构化 key-value 格式
  • 在操作开始和结束都记录日志
  • 包含 actionresourceresult 字段
8.2 DON'T ❌
  • 不要使用 log.Printffmt.Println
  • 不要硬编码字段名(如 "user_id" 改用 logger.FieldUserID
  • 不要在日志中记录敏感信息(密码、token)
  • 不要忽略 context 直接创建新 Logger
  • 不要使用过于模糊的日志消息(如 "error occurred")

9. 扩展阅读

Documentation

Overview

Package logger 提供请求范围的日志工具

该包提供了一套完整的日志解决方案,包括: - RequestLogger: 请求范围的日志记录器,支持通过 context 传递 - 标准字段定义: 统一的日志字段命名规范 - GORM 日志适配器: 将 GORM 日志输出到结构化日志系统 - Redis 日志钩子: 记录 Redis 命令执行情况 - HTTP 日志中间件: Gin 框架的请求日志中间件 - gRPC 日志拦截器: gRPC 服务端日志拦截器

设计目标: 1. 上下文传递: 通过 context.Context 传递 Logger,确保追踪信息不丢失 2. 结构化日志: 所有日志必须是 key-value 结构,便于机器解析 3. 字段标准化: 统一字段命名,提供常量定义 4. 分层日志: 不同层级(HTTP/gRPC/Service/DB)有明确的日志策略 5. 零侵入性: 业务代码只需调用 logger.L(ctx),无需关心底层实现

基本用法:

func (s *userService) CreateUser(ctx context.Context, dto CreateUserDTO) (*User, error) {
    l := logger.L(ctx)
    l.Infow("开始创建用户",
        "action", logger.ActionCreate,
        "resource", logger.ResourceUser,
    )
    // ...
}

该包设计为可独立使用,未来可迁移到 component-base 供多个项目共享。

Index

Constants

View Source
const (
	FieldTraceID   = "trace_id"
	FieldSpanID    = "span_id"
	FieldRequestID = "request_id"
)

追踪相关字段

View Source
const (
	FieldUserID    = "user_id"
	FieldAccountID = "account_id"
	FieldTenantID  = "tenant_id"
	FieldClientIP  = "client_ip"
	FieldUserAgent = "user_agent"
)

身份相关字段

View Source
const (
	FieldAction     = "action"
	FieldResource   = "resource"
	FieldResourceID = "resource_id"
	FieldResult     = "result"
)

操作相关字段

View Source
const (
	FieldDurationMS = "duration_ms"
	FieldLatency    = "latency"
)

性能相关字段

View Source
const (
	FieldError      = "error"
	FieldErrorCode  = "error_code"
	FieldStackTrace = "stack_trace"
)

错误相关字段

View Source
const (
	FieldMethod     = "method"
	FieldPath       = "path"
	FieldQuery      = "query"
	FieldStatusCode = "status_code"
)

HTTP 相关字段

View Source
const (
	FieldGRPCMethod  = "grpc.method"
	FieldGRPCService = "grpc.service"
	FieldGRPCCode    = "grpc.code"
)

gRPC 相关字段

View Source
const (
	ActionCreate   = "create"
	ActionRead     = "read"
	ActionUpdate   = "update"
	ActionDelete   = "delete"
	ActionList     = "list"
	ActionLogin    = "login"
	ActionLogout   = "logout"
	ActionRegister = "register"
	ActionVerify   = "verify"
	ActionRefresh  = "refresh"
	ActionRevoke   = "revoke"
	ActionBind     = "bind"
	ActionUnbind   = "unbind"
)
View Source
const (
	ResourceUser         = "user"
	ResourceChild        = "child"
	ResourceGuardianship = "guardianship"
	ResourceCredential   = "credential"
	ResourceToken        = "token"
	ResourceSession      = "session"
)
View Source
const (
	ResultSuccess = "success"
	ResultFailed  = "failed"
	ResultDenied  = "denied"
	ResultTimeout = "timeout"
)
View Source
const (
	EventRequestStart = "request_start"
	EventRequestEnd   = "request_end"
	EventOperation    = "operation"
	EventAudit        = "audit"
	EventSecurity     = "security"
	EventPerformance  = "performance"
)
View Source
const (
	GormSilent gormlogger.LogLevel = iota + 1
	GormError
	GormWarn
	GormInfo
)

GORM 日志级别

Variables

This section is empty.

Functions

func ErrorFields

func ErrorFields(err error, code int) []log.Field

ErrorFields 生成错误相关字段

func EventField

func EventField(event string) log.Field

EventField 生成事件类型字段

func GRPCFields

func GRPCFields(method, service, code string) []log.Field

GRPCFields 生成 gRPC 相关字段

func HTTPFields

func HTTPFields(method, path, query string, statusCode int) []log.Field

HTTPFields 生成 HTTP 相关字段

func HTTPLogger

func HTTPLogger() gin.HandlerFunc

HTTPLogger 返回 Gin HTTP 日志中间件

func HTTPLoggerWithConfig

func HTTPLoggerWithConfig(config HTTPLoggerConfig) gin.HandlerFunc

HTTPLoggerWithConfig 返回带配置的 Gin HTTP 日志中间件

func NewGormLogger

func NewGormLogger(level int) gormlogger.Interface

NewGormLogger 创建 GORM 日志适配器 将 GORM 的日志输出适配到 component-base 的类型化日志系统

func NewGormLoggerWithConfig

func NewGormLoggerWithConfig(config GormConfig) gormlogger.Interface

NewGormLoggerWithConfig 使用指定配置创建 GORM 日志适配器

func OperationFields

func OperationFields(action, resource, resourceID string) []log.Field

OperationFields 生成操作相关字段

func ResultFields

func ResultFields(result string, durationMS int64) []log.Field

ResultFields 生成操作结果字段

func StreamServerLoggingInterceptor

func StreamServerLoggingInterceptor() grpc.StreamServerInterceptor

StreamServerLoggingInterceptor gRPC 流式服务端日志拦截器

func StreamServerLoggingInterceptorWithConfig

func StreamServerLoggingInterceptorWithConfig(config GRPCServerLoggerConfig) grpc.StreamServerInterceptor

StreamServerLoggingInterceptorWithConfig 带配置的 gRPC 流式服务端日志拦截器

func UnaryServerLoggingInterceptor

func UnaryServerLoggingInterceptor() grpc.UnaryServerInterceptor

UnaryServerLoggingInterceptor gRPC 一元服务端日志拦截器

func UnaryServerLoggingInterceptorWithConfig

func UnaryServerLoggingInterceptorWithConfig(config GRPCServerLoggerConfig) grpc.UnaryServerInterceptor

UnaryServerLoggingInterceptorWithConfig 带配置的 gRPC 一元服务端日志拦截器

func UserFields

func UserFields(userID, accountID, tenantID string) []log.Field

UserFields 生成用户相关字段

func WithLogger

func WithLogger(ctx context.Context, logger *RequestLogger) context.Context

WithLogger 将 RequestLogger 放入 context

Types

type GRPCServerLoggerConfig

type GRPCServerLoggerConfig struct {
	// LogRequestPayload 是否记录请求载荷
	LogRequestPayload bool
	// LogResponsePayload 是否记录响应载荷
	LogResponsePayload bool
	// MaxPayloadSize 最大记录的载荷大小
	MaxPayloadSize int
	// SkipMethods 跳过日志记录的方法列表
	SkipMethods []string
}

GRPCServerLoggerConfig gRPC 服务端日志配置

func DefaultGRPCServerLoggerConfig

func DefaultGRPCServerLoggerConfig() GRPCServerLoggerConfig

DefaultGRPCServerLoggerConfig 默认 gRPC 服务端日志配置

type GormConfig

type GormConfig struct {
	// SlowThreshold 慢查询阈值,超过此时间会记录警告
	SlowThreshold time.Duration
	// Colorful 是否使用彩色输出(仅开发环境)
	Colorful bool
	// LogLevel 日志级别
	LogLevel gormlogger.LogLevel
}

GormConfig 定义 GORM 日志配置

func DefaultGormConfig

func DefaultGormConfig() GormConfig

DefaultGormConfig 返回默认 GORM 日志配置

type HTTPLoggerConfig

type HTTPLoggerConfig struct {
	// Tag 日志标签
	Tag string
	// SkipPaths 跳过日志记录的路径
	SkipPaths []string
	// LogRequestHeaders 是否记录请求头
	LogRequestHeaders bool
	// LogRequestBody 是否记录请求体
	LogRequestBody bool
	// LogResponseHeaders 是否记录响应头
	LogResponseHeaders bool
	// LogResponseBody 是否记录响应体
	LogResponseBody bool
	// MaskSensitiveData 是否对敏感数据脱敏
	MaskSensitiveData bool
	// MaxBodyBytes 最大记录的 body 大小
	MaxBodyBytes int64
	// RequestIDKey context 中 Request ID 的 key
	RequestIDKey string
}

HTTPLoggerConfig 定义 HTTP 日志中间件的可配置项

func DefaultHTTPLoggerConfig

func DefaultHTTPLoggerConfig() HTTPLoggerConfig

DefaultHTTPLoggerConfig 返回默认配置

type MongoHook added in v0.4.0

type MongoHook struct {
	// contains filtered or unexported fields
}

MongoHook MongoDB 命令执行钩子 用于记录 MongoDB 命令执行情况,类似于 GORM logger 和 Redis Hook

func NewMongoHook added in v0.4.0

func NewMongoHook(enabled bool, slowThreshold time.Duration) *MongoHook

NewMongoHook 创建 MongoDB 钩子 enabled: 是否启用日志记录 slowThreshold: 慢命令阈值(超过此时间会记录警告)

func NewMongoHookWithConfig added in v0.4.0

func NewMongoHookWithConfig(config MongoHookConfig) *MongoHook

NewMongoHookWithConfig 使用指定配置创建 MongoDB 钩子

func (*MongoHook) CommandMonitor added in v0.4.0

func (h *MongoHook) CommandMonitor() *event.CommandMonitor

CommandMonitor 返回 MongoDB 命令监控器 用于监控所有 MongoDB 命令的执行

func (*MongoHook) PoolMonitor added in v0.4.0

func (h *MongoHook) PoolMonitor() *event.PoolMonitor

PoolMonitor 返回 MongoDB 连接池监控器 用于监控连接池的创建、关闭等事件

func (*MongoHook) ServerMonitor added in v0.4.0

func (h *MongoHook) ServerMonitor() *event.ServerMonitor

ServerMonitor 返回 MongoDB 服务器监控器 用于监控服务器心跳、状态变更等事件

type MongoHookConfig added in v0.4.0

type MongoHookConfig struct {
	// Enabled 是否启用日志记录
	Enabled bool
	// SlowThreshold 慢命令阈值(超过此时间会记录警告)
	SlowThreshold time.Duration
	// LogCommandDetail 是否记录命令详情(建议仅在调试时开启)
	LogCommandDetail bool
	// LogReplyDetail 是否记录响应详情(建议仅在调试时开启)
	LogReplyDetail bool
	// LogHeartbeat 是否记录心跳日志(默认 false)
	LogHeartbeat bool
	// LogStarted 是否记录命令开始日志(默认 false,只记录完成)
	LogStarted bool
	// IgnoredCommands 忽略的命令列表(降噪)
	IgnoredCommands []string
	// SlowReadThreshold 慢读命令阈值(0 表示使用 SlowThreshold)
	SlowReadThreshold time.Duration
	// SlowWriteThreshold 慢写命令阈值(0 表示使用 SlowThreshold)
	SlowWriteThreshold time.Duration
}

MongoHookConfig MongoDB 钩子配置

func DefaultMongoHookConfig added in v0.4.0

func DefaultMongoHookConfig() MongoHookConfig

DefaultMongoHookConfig 返回默认 MongoDB 钩子配置

type RedisHook

type RedisHook struct {
	// contains filtered or unexported fields
}

RedisHook Redis 命令执行钩子 用于记录 Redis 命令执行情况,类似于 GORM logger

func NewRedisHook

func NewRedisHook(enabled bool, slowThreshold time.Duration) *RedisHook

NewRedisHook 创建 Redis 钩子 enabled: 是否启用日志记录 slowThreshold: 慢命令阈值(超过此时间会记录警告)

func NewRedisHookWithConfig

func NewRedisHookWithConfig(config RedisHookConfig) *RedisHook

NewRedisHookWithConfig 使用指定配置创建 Redis 钩子

func (*RedisHook) DialHook

func (h *RedisHook) DialHook(next redis.DialHook) redis.DialHook

DialHook 拨号钩子

func (*RedisHook) ProcessHook

func (h *RedisHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook

ProcessHook 命令执行钩子

func (*RedisHook) ProcessPipelineHook

func (h *RedisHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook

ProcessPipelineHook 管道命令钩子

type RedisHookConfig

type RedisHookConfig struct {
	// Enabled 是否启用日志记录
	Enabled bool
	// SlowThreshold 慢命令阈值(超过此时间会记录警告)
	SlowThreshold time.Duration
}

RedisHookConfig Redis 钩子配置

func DefaultRedisHookConfig

func DefaultRedisHookConfig() RedisHookConfig

DefaultRedisHookConfig 返回默认 Redis 钩子配置

type RequestLogger

type RequestLogger struct {
	// contains filtered or unexported fields
}

RequestLogger 请求范围的日志记录器 封装了 log.Logger,预设了追踪字段(trace_id, span_id, request_id 等)

func Default added in v0.4.0

func Default() *RequestLogger

Default 返回默认的 RequestLogger(无上下文场景使用) 适用场景: - 程序启动初始化 - 后台任务/定时任务 - 独立的 goroutine - 不在请求链路中的日志记录

示例:

logger.Default().Info("Application started")
logger.Default().WithField("version", "1.0.0").Info("Service initialized")

func L

L 从 context 获取 RequestLogger 如果 context 中没有 Logger,返回一个带有追踪信息的默认 Logger

func New added in v0.4.0

func New(fields ...log.Field) *RequestLogger

New 创建一个带有自定义字段的 RequestLogger(无上下文场景使用) 适用于需要预设一些固定字段的场景,如: - 后台任务标识 - 模块名称 - 服务名称

示例:

taskLogger := logger.New(log.String("task", "data_sync"), log.String("module", "background"))
taskLogger.Info("Task started")

func NewRequestLogger

func NewRequestLogger(ctx context.Context, fields ...log.Field) *RequestLogger

NewRequestLogger 创建请求范围的 Logger 自动从 context 中提取 trace_id, span_id, request_id 等追踪信息

func (*RequestLogger) Debug

func (l *RequestLogger) Debug(msg string, fields ...log.Field)

Debug 记录 Debug 级别日志

func (*RequestLogger) Debugw

func (l *RequestLogger) Debugw(msg string, keysAndValues ...interface{})

Debugw 记录 Debug 级别日志(key-value 格式)

func (*RequestLogger) Error

func (l *RequestLogger) Error(msg string, fields ...log.Field)

Error 记录 Error 级别日志

func (*RequestLogger) Errorw

func (l *RequestLogger) Errorw(msg string, keysAndValues ...interface{})

Errorw 记录 Error 级别日志(key-value 格式)

func (*RequestLogger) Fields

func (l *RequestLogger) Fields() []log.Field

Fields 返回当前 Logger 的所有字段(只读)

func (*RequestLogger) Info

func (l *RequestLogger) Info(msg string, fields ...log.Field)

Info 记录 Info 级别日志

func (*RequestLogger) Infow

func (l *RequestLogger) Infow(msg string, keysAndValues ...interface{})

Infow 记录 Info 级别日志(key-value 格式)

func (*RequestLogger) Warn

func (l *RequestLogger) Warn(msg string, fields ...log.Field)

Warn 记录 Warn 级别日志

func (*RequestLogger) Warnw

func (l *RequestLogger) Warnw(msg string, keysAndValues ...interface{})

Warnw 记录 Warn 级别日志(key-value 格式)

func (*RequestLogger) WithField

func (l *RequestLogger) WithField(key string, value interface{}) *RequestLogger

WithField 创建一个带有单个额外字段的新 Logger

func (*RequestLogger) WithFields

func (l *RequestLogger) WithFields(fields ...log.Field) *RequestLogger

WithFields 创建一个带有额外字段的新 Logger(不可变设计)

Directories

Path Synopsis
Package examples 提供 logger 包的使用示例
Package examples 提供 logger 包的使用示例

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL