package database import ( "context" "time" "veza-backend-api/internal/metrics" "go.uber.org/zap" "gorm.io/gorm" ) // PerformanceMonitor monitors database query performance and logs slow queries // BE-DB-018: Add database performance monitoring with slow query logging type PerformanceMonitor struct { logger *zap.Logger slowQueryThreshold time.Duration // Threshold for slow query logging enabled bool } // PerformanceMonitorConfig contains configuration for the performance monitor type PerformanceMonitorConfig struct { Logger *zap.Logger SlowQueryThreshold time.Duration // Default: 1 second Enabled bool // Default: true } // NewPerformanceMonitor creates a new performance monitor func NewPerformanceMonitor(config PerformanceMonitorConfig) *PerformanceMonitor { threshold := config.SlowQueryThreshold if threshold == 0 { threshold = 1 * time.Second // Default: 1 second } enabled := config.Enabled if config.Logger == nil { enabled = false // Disable if no logger } return &PerformanceMonitor{ logger: config.Logger, slowQueryThreshold: threshold, enabled: enabled, } } // WrapGORM wraps a GORM DB instance with performance monitoring func (pm *PerformanceMonitor) WrapGORM(db *gorm.DB) *gorm.DB { if !pm.enabled { return db } // Register GORM callback to monitor queries db.Callback().Query().Before("gorm:query").Register("performance_monitor:before_query", pm.beforeQuery) db.Callback().Query().After("gorm:query").Register("performance_monitor:after_query", pm.afterQuery) db.Callback().Create().Before("gorm:create").Register("performance_monitor:before_create", pm.beforeQuery) db.Callback().Create().After("gorm:create").Register("performance_monitor:after_create", pm.afterQuery) db.Callback().Update().Before("gorm:update").Register("performance_monitor:before_update", pm.beforeQuery) db.Callback().Update().After("gorm:update").Register("performance_monitor:after_update", pm.afterQuery) db.Callback().Delete().Before("gorm:delete").Register("performance_monitor:before_delete", pm.beforeQuery) db.Callback().Delete().After("gorm:delete").Register("performance_monitor:after_delete", pm.afterQuery) return db } // beforeQuery is called before a query executes func (pm *PerformanceMonitor) beforeQuery(db *gorm.DB) { // Store start time in the statement context ctx := context.WithValue(db.Statement.Context, "query_start_time", time.Now()) db.Statement.Context = ctx } // afterQuery is called after a query executes func (pm *PerformanceMonitor) afterQuery(db *gorm.DB) { // Get start time from context startTime, ok := db.Statement.Context.Value("query_start_time").(time.Time) if !ok { return } duration := time.Since(startTime) // Extract operation type and table name operation := "UNKNOWN" if db.Statement != nil { // Try to determine operation from SQL sqlStr := db.Statement.SQL.String() if len(sqlStr) > 0 { upperSQL := sqlStr[:min(10, len(sqlStr))] switch { case contains(upperSQL, "SELECT"): operation = "SELECT" case contains(upperSQL, "INSERT"): operation = "INSERT" case contains(upperSQL, "UPDATE"): operation = "UPDATE" case contains(upperSQL, "DELETE"): operation = "DELETE" } } table := "unknown" if db.Statement.Table != "" { table = db.Statement.Table } else if db.Statement.Model != nil { // Try to get table name from model if stmt := db.Statement; stmt != nil { table = stmt.Schema.Table } } // Record metrics metrics.RecordDBQuery(operation, table, duration) // Log slow queries if duration >= pm.slowQueryThreshold { pm.logSlowQuery(operation, table, duration, db) } } } // logSlowQuery logs a slow query with details func (pm *PerformanceMonitor) logSlowQuery(operation, table string, duration time.Duration, db *gorm.DB) { fields := []zap.Field{ zap.String("operation", operation), zap.String("table", table), zap.Duration("duration", duration), zap.Duration("threshold", pm.slowQueryThreshold), } // Add SQL if available (be careful with sensitive data) if db.Statement != nil { sqlStr := db.Statement.SQL.String() if len(sqlStr) > 0 { // Truncate SQL if too long if len(sqlStr) > 500 { sqlStr = sqlStr[:500] + "..." } fields = append(fields, zap.String("sql", sqlStr)) } } // Add error if query failed if db.Error != nil { fields = append(fields, zap.Error(db.Error)) pm.logger.Warn("Slow query with error", fields...) } else { pm.logger.Warn("Slow query detected", fields...) } // Record slow query metric metrics.RecordSlowQuery(operation, table, duration) } // MeasureQuery measures a query execution time and logs if slow // This is a helper function for manual query measurement func (pm *PerformanceMonitor) MeasureQuery(operation, table string, fn func() error) error { if !pm.enabled { return fn() } start := time.Now() err := fn() duration := time.Since(start) // Record metrics metrics.RecordDBQuery(operation, table, duration) // Log if slow if duration >= pm.slowQueryThreshold { fields := []zap.Field{ zap.String("operation", operation), zap.String("table", table), zap.Duration("duration", duration), zap.Duration("threshold", pm.slowQueryThreshold), } if err != nil { fields = append(fields, zap.Error(err)) pm.logger.Warn("Slow query with error", fields...) } else { pm.logger.Warn("Slow query detected", fields...) } // Record slow query metric metrics.RecordSlowQuery(operation, table, duration) } return err } // Helper functions func min(a, b int) int { if a < b { return a } return b } func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && s[:len(substr)] == substr) }