Add monitoring on queries with sentry alert + Fix check position list in db for backtest
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user