package data import ( "context" "errors" "fmt" "io" "log/slog" "time" "gorm.io/gorm/logger" ) // gormLogger forwards GORM diagnostics through the application logger so SQL // entries participate in the same daily/category files and error sink as the // rest of the service. Slow queries use the administration contract's 200ms // threshold. type gormLogger struct { logger *slog.Logger slowThreshold time.Duration level logger.LogLevel } func newGormLogger(log *slog.Logger, level logger.LogLevel) *gormLogger { if log == nil { log = slog.New(slog.NewTextHandler(io.Discard, nil)) } return &gormLogger{logger: log, slowThreshold: 200 * time.Millisecond, level: level} } func (g *gormLogger) LogMode(level logger.LogLevel) logger.Interface { next := *g next.level = level return &next } func (g *gormLogger) Info(ctx context.Context, message string, args ...any) { g.logger.InfoContext(ctx, fmt.Sprintf(message, args...), "mod", "sql", "gorm_logger", true) } func (g *gormLogger) Warn(ctx context.Context, message string, args ...any) { g.logger.WarnContext(ctx, fmt.Sprintf(message, args...), "mod", "sql", "gorm_logger", true) } func (g *gormLogger) Error(ctx context.Context, message string, args ...any) { g.logger.ErrorContext(ctx, fmt.Sprintf(message, args...), "mod", "sql", "gorm_logger", true) } func (g *gormLogger) Trace(ctx context.Context, begin time.Time, query func() (string, int64), queryErr error) { if g.level <= logger.Silent { return } elapsed := time.Since(begin) sql, rows := query() fields := []any{"mod", "sql", "gorm_logger", true, "sql", sql, "rows", rows, "elapsed_ms", elapsed.Milliseconds()} switch { case queryErr != nil && g.level >= logger.Error && !errors.Is(queryErr, logger.ErrRecordNotFound): fields = append(fields, "error", queryErr) g.logger.ErrorContext(ctx, "SQL 执行错误", fields...) case elapsed > g.slowThreshold && g.level >= logger.Warn: g.logger.WarnContext(ctx, "SQL 慢查询", fields...) case g.level >= logger.Info: g.logger.InfoContext(ctx, "SQL", fields...) } }