- Increased thresholds for maximum query and method executions per window to 500 and 250, respectively, to reduce false positives in loop detection. - Enabled logging of slow queries only, improving performance by reducing log volume. - Adjusted SQL query logging to capture only warnings and errors, further optimizing logging efficiency. - Updated various settings across appsettings files to reflect these changes, ensuring consistency in configuration.
224 lines
8.9 KiB
C#
224 lines
8.9 KiB
C#
using System.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Managing.Infrastructure.Databases.PostgreSql;
|
|
|
|
/// <summary>
|
|
/// Base repository class with comprehensive SQL query logging and monitoring
|
|
/// Provides automatic query tracking, loop detection, and performance monitoring
|
|
/// </summary>
|
|
public abstract class BaseRepositoryWithLogging
|
|
{
|
|
protected readonly ManagingDbContext _context;
|
|
protected readonly ILogger<SqlQueryLogger> _logger;
|
|
protected readonly SentrySqlMonitoringService _sentryMonitoringService;
|
|
protected readonly string _repositoryName;
|
|
|
|
protected BaseRepositoryWithLogging(ManagingDbContext context, ILogger<SqlQueryLogger> logger, SentrySqlMonitoringService sentryMonitoringService)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
_sentryMonitoringService = sentryMonitoringService;
|
|
_repositoryName = GetType().Name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a database operation with lightweight logging and monitoring
|
|
/// Only logs slow queries (>2000ms) and errors to minimize performance impact
|
|
/// </summary>
|
|
/// <typeparam name="T">Return type of the operation</typeparam>
|
|
/// <param name="operation">The database operation to execute</param>
|
|
/// <param name="methodName">Name of the calling method</param>
|
|
/// <param name="parameters">Parameters passed to the operation</param>
|
|
/// <returns>Result of the operation</returns>
|
|
protected async Task<T> ExecuteWithLoggingAsync<T>(
|
|
Func<Task<T>> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a database operation with lightweight logging and monitoring (void return)
|
|
/// Only logs slow queries (>2000ms) and errors to minimize performance impact
|
|
/// </summary>
|
|
/// <param name="operation">The database operation to execute</param>
|
|
/// <param name="methodName">Name of the calling method</param>
|
|
/// <param name="parameters">Parameters passed to the operation</param>
|
|
protected async Task ExecuteWithLoggingAsync(
|
|
Func<Task> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a query pattern for tracking purposes
|
|
/// </summary>
|
|
/// <param name="methodName">Name of the method</param>
|
|
/// <param name="parameters">Method parameters</param>
|
|
/// <returns>Query pattern string</returns>
|
|
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)})";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs a potential performance issue
|
|
/// </summary>
|
|
/// <param name="operation">Operation description</param>
|
|
/// <param name="duration">Operation duration</param>
|
|
/// <param name="threshold">Performance threshold</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends slow query alert to Sentry asynchronously (fire and forget)
|
|
/// </summary>
|
|
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");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends SQL error to Sentry asynchronously (fire and forget)
|
|
/// </summary>
|
|
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");
|
|
}
|
|
}
|
|
}
|