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");
}
}
}