Add monitoring on queries with sentry alert + Fix check position list in db for backtest

This commit is contained in:
2025-10-10 00:15:02 +07:00
parent ffb98fe359
commit e4c2f8b7a5
24 changed files with 3340 additions and 179 deletions

View File

@@ -0,0 +1,223 @@
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 slow query (>2000ms) and logging is enabled
if (stopwatch.Elapsed.TotalMilliseconds > 2000 && _sentryMonitoringService.IsLoggingEnabled())
{
_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 slow query (>2000ms) and logging is enabled
if (stopwatch.Elapsed.TotalMilliseconds > 2000 && _sentryMonitoringService.IsLoggingEnabled())
{
_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");
}
}
}