using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Managing.Infrastructure.Databases.PostgreSql; /// /// Base repository class with comprehensive SQL query logging and monitoring /// Provides automatic query tracking, loop detection, and performance monitoring /// public abstract class BaseRepositoryWithLogging { protected readonly ManagingDbContext _context; protected readonly ILogger _logger; protected readonly SentrySqlMonitoringService _sentryMonitoringService; protected readonly string _repositoryName; protected BaseRepositoryWithLogging(ManagingDbContext context, ILogger logger, SentrySqlMonitoringService sentryMonitoringService) { _context = context; _logger = logger; _sentryMonitoringService = sentryMonitoringService; _repositoryName = GetType().Name; } /// /// Executes a database operation with lightweight logging and monitoring /// Only logs slow queries (>2000ms) and errors to minimize performance impact /// /// Return type of the operation /// The database operation to execute /// Name of the calling method /// Parameters passed to the operation /// Result of the operation protected async Task ExecuteWithLoggingAsync( Func> operation, string methodName, params (string name, object value)[] parameters) { // Check if monitoring is enabled globally if (!_sentryMonitoringService.IsMonitoringEnabled()) { return await operation(); } var stopwatch = Stopwatch.StartNew(); var queryPattern = GenerateQueryPattern(methodName, parameters); try { var result = await operation(); stopwatch.Stop(); // Only log if it should be logged based on monitoring settings (respects LogSlowQueriesOnly and thresholds) if (_sentryMonitoringService.ShouldLogQuery(stopwatch.Elapsed)) { _logger.LogWarning( "[SLOW-SQL] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms", _repositoryName, methodName, queryPattern, stopwatch.Elapsed.TotalMilliseconds); // Send slow query alert to Sentry asynchronously if enabled if (_sentryMonitoringService.IsSentryEnabled()) { _ = Task.Run(() => SendSlowQueryToSentryAsync(queryPattern, stopwatch.Elapsed, methodName)); } } // Track query execution for loop detection if enabled (minimal overhead) if (_sentryMonitoringService.IsLoopDetectionEnabled()) { _context.TrackQueryExecution(queryPattern, stopwatch.Elapsed, _repositoryName, methodName); } return result; } catch (Exception ex) { stopwatch.Stop(); // Always log errors if logging is enabled if (_sentryMonitoringService.IsLoggingEnabled()) { _logger.LogError(ex, "[SQL-ERROR] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms", _repositoryName, methodName, queryPattern, stopwatch.Elapsed.TotalMilliseconds); } // Send SQL error to Sentry asynchronously if enabled if (_sentryMonitoringService.IsSentryEnabled()) { _ = Task.Run(() => SendSqlErrorToSentryAsync(queryPattern, stopwatch.Elapsed, ex, methodName)); } throw; } } /// /// Executes a database operation with lightweight logging and monitoring (void return) /// Only logs slow queries (>2000ms) and errors to minimize performance impact /// /// The database operation to execute /// Name of the calling method /// Parameters passed to the operation protected async Task ExecuteWithLoggingAsync( Func operation, string methodName, params (string name, object value)[] parameters) { // Check if monitoring is enabled globally if (!_sentryMonitoringService.IsMonitoringEnabled()) { await operation(); return; } var stopwatch = Stopwatch.StartNew(); var queryPattern = GenerateQueryPattern(methodName, parameters); try { await operation(); stopwatch.Stop(); // Only log if it should be logged based on monitoring settings (respects LogSlowQueriesOnly and thresholds) if (_sentryMonitoringService.ShouldLogQuery(stopwatch.Elapsed)) { _logger.LogWarning( "[SLOW-SQL] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms", _repositoryName, methodName, queryPattern, stopwatch.Elapsed.TotalMilliseconds); // Send slow query alert to Sentry asynchronously if enabled if (_sentryMonitoringService.IsSentryEnabled()) { _ = Task.Run(() => SendSlowQueryToSentryAsync(queryPattern, stopwatch.Elapsed, methodName)); } } // Track query execution for loop detection if enabled (minimal overhead) if (_sentryMonitoringService.IsLoopDetectionEnabled()) { _context.TrackQueryExecution(queryPattern, stopwatch.Elapsed, _repositoryName, methodName); } } catch (Exception ex) { stopwatch.Stop(); // Always log errors if logging is enabled if (_sentryMonitoringService.IsLoggingEnabled()) { _logger.LogError(ex, "[SQL-ERROR] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms", _repositoryName, methodName, queryPattern, stopwatch.Elapsed.TotalMilliseconds); } // Send SQL error to Sentry asynchronously if enabled if (_sentryMonitoringService.IsSentryEnabled()) { _ = Task.Run(() => SendSqlErrorToSentryAsync(queryPattern, stopwatch.Elapsed, ex, methodName)); } throw; } } /// /// Generates a query pattern for tracking purposes /// /// Name of the method /// Method parameters /// Query pattern string private string GenerateQueryPattern(string methodName, (string name, object value)[] parameters) { var paramStrings = parameters.Select(p => $"{p.name}={p.value?.GetType().Name ?? "null"}"); return $"{methodName}({string.Join(",", paramStrings)})"; } /// /// Logs a potential performance issue /// /// Operation description /// Operation duration /// Performance threshold protected void LogPerformanceIssue(string operation, TimeSpan duration, TimeSpan threshold) { if (duration > threshold) { _logger.LogWarning( "[SQL-PERFORMANCE] {Repository} | {Operation} took {Duration}ms (threshold: {Threshold}ms)", _repositoryName, operation, duration.TotalMilliseconds, threshold.TotalMilliseconds); } } /// /// Sends slow query alert to Sentry asynchronously (fire and forget) /// private async Task SendSlowQueryToSentryAsync(string queryPattern, TimeSpan executionTime, string methodName) { try { await _sentryMonitoringService.SendSlowQueryAlertAsync(_repositoryName, methodName, queryPattern, executionTime); } catch (Exception ex) { _logger.LogError(ex, "[SENTRY-ERROR] Failed to send slow query alert to Sentry"); } } /// /// Sends SQL error to Sentry asynchronously (fire and forget) /// private async Task SendSqlErrorToSentryAsync(string queryPattern, TimeSpan executionTime, Exception exception, string methodName) { try { await _sentryMonitoringService.SendSqlErrorAlertAsync(_repositoryName, methodName, queryPattern, executionTime, exception); } catch (Exception ex) { _logger.LogError(ex, "[SENTRY-ERROR] Failed to send SQL error alert to Sentry"); } } }