Skip to content

Feature: 添加结构化日志接口支持(Structured Logging Interface) #7646

@hupeh

Description

@hupeh

Describe the feature

希望 GORM 能够提供结构化日志接口,以便更好地与现代结构化日志库(如 slog、zap、zerolog 等)集成,而不需要解析格式化字符串。

目前 GORM 的日志接口只提供了 Printf(string, ...interface{}) 方法,这迫使用户必须解析格式化字符串来提取结构化字段,如执行时间、影响行数、SQL 语句和错误信息。这种方式既容易出错又效率低下。

建议的 API:

type StructuredLogger interface {
    LogQuery(ctx context.Context, level Level, info QueryInfo)
}

type QueryInfo struct {
    SQL           string        // SQL 语句
    RowsAffected  int64         // 影响行数
    Elapsed       time.Duration // 执行时长
    Error         error         // 错误信息
    SlowThreshold time.Duration // 慢查询阈值
    Source        string        // 调用来源
}

或者扩展现有的 Writer 接口:

type Writer interface {
    Printf(string, ...interface{})
    // 新增结构化日志方法
    LogStructured(level Level, fields map[string]interface{})
}

Motivation

1. 字符串解析容易出错

目前的方式需要解析各种格式字符串,例如:

  • "%s\n[info] "
  • "%s\n[%.3fms] [rows:%v] %s"
  • "%s %s\n[%.3fms] [rows:%v] %s"

这种方式非常脆弱,依赖于 GORM 内部的格式化逻辑,而这些格式可能会变化。我们在生产环境中就遇到了因为 data 切片与预期不符导致的 panic:

// 我们不得不编写的脆弱代码
data = data[n:]
if f64, ok := data[0].(float64); ok { // panic: index out of range
    // ...
}

2. 更好地集成现代日志生态系统

现代 Go 应用程序使用的结构化日志库(slog、zap、zerolog)提供了:

  • 类型安全的字段处理
  • 更好的性能(无需字符串格式化)
  • 应用程序中一致的日志结构
  • 更好的日志聚合和搜索能力(在 ELK、Loki、CloudWatch 等系统中)

3. 性能优势

字符串格式化和后续解析会增加不必要的开销:

格式化字符串 → 解析字符串 → 转换为结构化字段

使用结构化日志:

直接使用结构化字段

4. 可维护性

GORM 日志格式的变化可能会破坏用户代码。结构化接口能提供稳定的契约。

真实案例

目前,为了与结构化日志集成,我们需要编写这样的代码:

func (w logWriter) Printf(msg string, data ...any) {
    // 解析格式字符串
    parts := strings.SplitN(msg, "\n", 2)
    if len(parts) != 2 {
        return // 无法解析,丢失日志
    }
    
    // 根据前缀猜测格式
    if strings.HasPrefix(parts[1], "[info] ") {
        // 手动提取字段...
    }
    
    // 更多脆弱的解析...
    n := strings.Count(parts[0], "%s")
    data = data[n:]  // 危险!
    elapsed := data[0].(float64)  // 可能 panic!
    rows := data[1]
    sql := data[2:]
    // ...
}

如果有结构化日志支持,代码可以简化为:

func (l *StructuredLogger) LogQuery(ctx context.Context, level Level, info QueryInfo) {
    l.logger.Info("数据库查询",
        "耗时", info.Elapsed,
        "影响行数", info.RowsAffected,
        "SQL", info.SQL,
        "错误", info.Error,
    )
}

优势对比:

方面 当前方式 结构化日志
可靠性 容易因格式变化而崩溃 稳定的接口契约
性能 需要格式化+解析 直接传递字段
可读性 大量字符串解析代码 简洁清晰
集成性 需要手动适配各种日志库 原生支持结构化输出

Related Issues

这将是一个向后兼容的功能增强,能够显著改善开发者在将 GORM 与现代日志基础设施集成时的体验。


补充说明:

这个功能对于企业级应用特别重要,因为:

  1. 生产环境需要可靠的日志记录,不能因为格式解析失败而丢失重要信息
  2. 需要将数据库日志与应用日志统一管理和分析
  3. 性能敏感的场景下,字符串格式化的开销不可忽视

希望 GORM 团队能够考虑这个功能请求,谢谢!

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions