Update configuration settings and logging behavior for SQL monitoring

- 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.
This commit is contained in:
2025-11-24 01:02:53 +07:00
parent 372d19f840
commit fef66f6d7b
10 changed files with 56 additions and 53 deletions

View File

@@ -132,22 +132,18 @@ builder.Services.AddDbContext<ManagingDbContext>((serviceProvider, options) =>
// Enable service provider caching for better performance // Enable service provider caching for better performance
options.EnableServiceProviderCaching(); options.EnableServiceProviderCaching();
// Enable comprehensive SQL query logging for monitoring and debugging // Enable SQL query logging for warnings and errors only (reduced logging)
var logger = serviceProvider.GetRequiredService<ILogger<ManagingDbContext>>(); var logger = serviceProvider.GetRequiredService<ILogger<ManagingDbContext>>();
var sentryMonitoringService = serviceProvider.GetRequiredService<SentrySqlMonitoringService>(); var sentryMonitoringService = serviceProvider.GetRequiredService<SentrySqlMonitoringService>();
options.LogTo(msg => options.LogTo(msg =>
{ {
// Log SQL queries with enhanced formatting // Log only warnings and errors to reduce log volume
if (msg.Contains("Executed DbCommand") || msg.Contains("Executing DbCommand")) if (msg.Contains("Warning") || msg.Contains("Error"))
{
Console.WriteLine($"[EF-SQL] {msg}");
}
else if (msg.Contains("Warning") || msg.Contains("Error"))
{ {
Console.WriteLine($"[EF-WARNING] {msg}"); Console.WriteLine($"[EF-WARNING] {msg}");
} }
}, LogLevel.Information); // Log all SQL operations for monitoring }, LogLevel.Warning); // Log only warnings and errors to reduce SQL log volume
}, ServiceLifetime.Scoped); // Explicitly specify scoped lifetime for proper request isolation }, ServiceLifetime.Scoped); // Explicitly specify scoped lifetime for proper request isolation
// Add specific health checks for databases and other services // Add specific health checks for databases and other services

View File

@@ -37,7 +37,9 @@
"LoggingEnabled": false, "LoggingEnabled": false,
"SentryEnabled": false, "SentryEnabled": false,
"LoopDetectionEnabled": true, "LoopDetectionEnabled": true,
"LogErrorsOnly": true "LogErrorsOnly": true,
"MaxQueryExecutionsPerWindow": 500,
"MaxMethodExecutionsPerWindow": 250
}, },
"Cors": { "Cors": {
"AllowedOrigins": [ "AllowedOrigins": [

View File

@@ -34,7 +34,7 @@
"LoggingEnabled": true, "LoggingEnabled": true,
"SentryEnabled": false, "SentryEnabled": false,
"LoopDetectionEnabled": true, "LoopDetectionEnabled": true,
"LogSlowQueriesOnly": false "LogSlowQueriesOnly": true
}, },
"RunOrleansGrains": true, "RunOrleansGrains": true,
"AllowedHosts": "*", "AllowedHosts": "*",

View File

@@ -96,12 +96,12 @@
"LoopDetectionEnabled": true, "LoopDetectionEnabled": true,
"PerformanceMonitoringEnabled": true, "PerformanceMonitoringEnabled": true,
"LoopDetectionWindowSeconds": 60, "LoopDetectionWindowSeconds": 60,
"MaxQueryExecutionsPesrWindow": 100, "MaxQueryExecutionsPerWindow": 500,
"MaxMethodExecutionsPerWindow": 50, "MaxMethodExecutionsPerWindow": 250,
"LongRunningQueryThresholdMs": 1000, "LongRunningQueryThresholdMs": 1000,
"SentryAlertThreshold": 5, "SentryAlertThreshold": 5,
"SlowQueryThresholdMs": 1500, "SlowQueryThresholdMs": 1500,
"LogSlowQueriesOnly": false, "LogSlowQueriesOnly": true,
"LogErrorsOnly": false, "LogErrorsOnly": false,
"DataRetentionMinutes": 300 "DataRetentionMinutes": 300
}, },

View File

@@ -50,8 +50,8 @@ public abstract class BaseRepositoryWithLogging
var result = await operation(); var result = await operation();
stopwatch.Stop(); stopwatch.Stop();
// Only log if slow query (>2000ms) and logging is enabled // Only log if it should be logged based on monitoring settings (respects LogSlowQueriesOnly and thresholds)
if (stopwatch.Elapsed.TotalMilliseconds > 2000 && _sentryMonitoringService.IsLoggingEnabled()) if (_sentryMonitoringService.ShouldLogQuery(stopwatch.Elapsed))
{ {
_logger.LogWarning( _logger.LogWarning(
"[SLOW-SQL] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms", "[SLOW-SQL] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms",
@@ -121,8 +121,8 @@ public abstract class BaseRepositoryWithLogging
await operation(); await operation();
stopwatch.Stop(); stopwatch.Stop();
// Only log if slow query (>2000ms) and logging is enabled // Only log if it should be logged based on monitoring settings (respects LogSlowQueriesOnly and thresholds)
if (stopwatch.Elapsed.TotalMilliseconds > 2000 && _sentryMonitoringService.IsLoggingEnabled()) if (_sentryMonitoringService.ShouldLogQuery(stopwatch.Elapsed))
{ {
_logger.LogWarning( _logger.LogWarning(
"[SLOW-SQL] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms", "[SLOW-SQL] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms",

View File

@@ -768,12 +768,14 @@ public class ManagingDbContext : DbContext
// Check for potential loops with Sentry integration // Check for potential loops with Sentry integration
var isLoopDetected = _sentryMonitoringService.TrackQueryExecution(repositoryName, methodName, queryPattern, executionTime); var isLoopDetected = _sentryMonitoringService.TrackQueryExecution(repositoryName, methodName, queryPattern, executionTime);
// Log query execution details // Only log query execution details if it should be logged based on monitoring settings
var logLevel = executionTime.TotalMilliseconds > 1000 ? LogLevel.Warning : LogLevel.Debug; if (_sentryMonitoringService.ShouldLogQuery(executionTime))
_logger.Log(logLevel, {
_logger.LogWarning(
"[SQL-QUERY-TRACKED] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms | Count: {Count}", "[SQL-QUERY-TRACKED] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms | Count: {Count}",
repositoryName, methodName, queryPattern, executionTime.TotalMilliseconds, repositoryName, methodName, queryPattern, executionTime.TotalMilliseconds,
_queryExecutionCounts[queryPattern]); _queryExecutionCounts[queryPattern]);
}
// Alert on potential loops // Alert on potential loops
if (isLoopDetected) if (isLoopDetected)

View File

@@ -71,13 +71,13 @@ public class SentrySqlMonitoringService
var sentryTags = new Dictionary<string, string>(); var sentryTags = new Dictionary<string, string>();
var sentryExtras = new Dictionary<string, object>(); var sentryExtras = new Dictionary<string, object>();
// Check execution frequency // Check execution frequency (increased threshold to reduce false positives)
if (executionsPerMinute > 20) if (executionsPerMinute > 100)
{ {
isLoopDetected = true; isLoopDetected = true;
reasons.Add($"High frequency: {executionsPerMinute:F1} executions/minute"); reasons.Add($"High frequency: {executionsPerMinute:F1} executions/minute");
if (executionsPerMinute > 50) // Critical frequency threshold if (executionsPerMinute > 200) // Critical frequency threshold (increased)
{ {
isCriticalAlert = true; isCriticalAlert = true;
sentryTags["alert_level"] = "critical"; sentryTags["alert_level"] = "critical";
@@ -99,8 +99,8 @@ public class SentrySqlMonitoringService
} }
} }
// Check for rapid successive executions // Check for rapid successive executions (increased threshold to reduce false positives)
if (tracker.ExecutionCount > 5 && timeSinceFirst.TotalSeconds < 10) if (tracker.ExecutionCount > 20 && timeSinceFirst.TotalSeconds < 10)
{ {
isLoopDetected = true; isLoopDetected = true;
isCriticalAlert = true; isCriticalAlert = true;
@@ -109,8 +109,8 @@ public class SentrySqlMonitoringService
sentryTags["issue_type"] = "rapid_execution"; sentryTags["issue_type"] = "rapid_execution";
} }
// Check for consistently slow queries // Check for consistently slow queries (increased threshold to reduce false positives)
if (tracker.ExecutionCount > 3 && tracker.AverageExecutionTime.TotalMilliseconds > 1000) if (tracker.ExecutionCount > 10 && tracker.AverageExecutionTime.TotalMilliseconds > 1000)
{ {
isLoopDetected = true; isLoopDetected = true;
reasons.Add($"Consistently slow: {tracker.AverageExecutionTime.TotalMilliseconds:F0}ms average"); reasons.Add($"Consistently slow: {tracker.AverageExecutionTime.TotalMilliseconds:F0}ms average");

View File

@@ -73,8 +73,8 @@ public class SqlLoopDetectionService
var isLoopDetected = false; var isLoopDetected = false;
var reasons = new List<string>(); var reasons = new List<string>();
// Check execution frequency // Check execution frequency (increased threshold to reduce false positives)
if (executionsPerMinute > 20) if (executionsPerMinute > 100)
{ {
isLoopDetected = true; isLoopDetected = true;
reasons.Add($"High frequency: {executionsPerMinute:F1} executions/minute"); reasons.Add($"High frequency: {executionsPerMinute:F1} executions/minute");
@@ -87,16 +87,16 @@ public class SqlLoopDetectionService
reasons.Add($"High count: {tracker.ExecutionCount} executions in {timeSinceFirst.TotalMinutes:F1} minutes"); reasons.Add($"High count: {tracker.ExecutionCount} executions in {timeSinceFirst.TotalMinutes:F1} minutes");
} }
// Check for rapid successive executions // Check for rapid successive executions (increased threshold to reduce false positives)
if (tracker.ExecutionCount > 5 && timeSinceFirst.TotalSeconds < 10) if (tracker.ExecutionCount > 20 && timeSinceFirst.TotalSeconds < 10)
{ {
isLoopDetected = true; isLoopDetected = true;
reasons.Add( reasons.Add(
$"Rapid execution: {tracker.ExecutionCount} executions in {timeSinceFirst.TotalSeconds:F1} seconds"); $"Rapid execution: {tracker.ExecutionCount} executions in {timeSinceFirst.TotalSeconds:F1} seconds");
} }
// Check for consistently slow queries // Check for consistently slow queries (increased threshold to reduce false positives)
if (tracker.ExecutionCount > 3 && tracker.AverageExecutionTime.TotalMilliseconds > 1000) if (tracker.ExecutionCount > 10 && tracker.AverageExecutionTime.TotalMilliseconds > 1000)
{ {
isLoopDetected = true; isLoopDetected = true;
reasons.Add($"Consistently slow: {tracker.AverageExecutionTime.TotalMilliseconds:F0}ms average"); reasons.Add($"Consistently slow: {tracker.AverageExecutionTime.TotalMilliseconds:F0}ms average");

View File

@@ -36,14 +36,14 @@ public class SqlMonitoringSettings
public int LoopDetectionWindowSeconds { get; set; } = 60; public int LoopDetectionWindowSeconds { get; set; } = 60;
/// <summary> /// <summary>
/// Maximum query executions per window for loop detection (default: 100) /// Maximum query executions per window for loop detection (default: 500)
/// </summary> /// </summary>
public int MaxQueryExecutionsPerWindow { get; set; } = 100; public int MaxQueryExecutionsPerWindow { get; set; } = 500;
/// <summary> /// <summary>
/// Maximum method executions per window for loop detection (default: 50) /// Maximum method executions per window for loop detection (default: 250)
/// </summary> /// </summary>
public int MaxMethodExecutionsPerWindow { get; set; } = 50; public int MaxMethodExecutionsPerWindow { get; set; } = 250;
/// <summary> /// <summary>
/// Threshold for long-running queries in milliseconds (default: 1000) /// Threshold for long-running queries in milliseconds (default: 1000)
@@ -61,9 +61,9 @@ public class SqlMonitoringSettings
public int SlowQueryThresholdMs { get; set; } = 2000; public int SlowQueryThresholdMs { get; set; } = 2000;
/// <summary> /// <summary>
/// Whether to log only slow queries (reduces overhead) (default: false) /// Whether to log only slow queries (reduces overhead) (default: true)
/// </summary> /// </summary>
public bool LogSlowQueriesOnly { get; set; } = false; public bool LogSlowQueriesOnly { get; set; } = true;
/// <summary> /// <summary>
/// Whether to log only errors (minimal overhead) (default: false) /// Whether to log only errors (minimal overhead) (default: false)

View File

@@ -34,6 +34,7 @@ public class SqlQueryLogger : IDisposable
/// <summary> /// <summary>
/// Logs the start of a database operation /// Logs the start of a database operation
/// Only logs at Debug level to reduce log volume (not logged by default)
/// </summary> /// </summary>
public void LogOperationStart(params (string name, object value)[] parameters) public void LogOperationStart(params (string name, object value)[] parameters)
{ {
@@ -42,20 +43,20 @@ public class SqlQueryLogger : IDisposable
_parameters[name] = value; _parameters[name] = value;
} }
_logger.LogInformation( _logger.LogDebug(
"[SQL-OP-START] {OperationId} | {Repository}.{Method} | Started at {StartTime}", "[SQL-OP-START] {OperationId} | {Repository}.{Method} | Started at {StartTime}",
_operationId, _repositoryName, _methodName, _startTime.ToString("HH:mm:ss.fff")); _operationId, _repositoryName, _methodName, _startTime.ToString("HH:mm:ss.fff"));
} }
/// <summary> /// <summary>
/// Logs a SQL query execution with timing and parameters /// Logs a SQL query execution with timing and parameters
/// Only logs errors and slow queries (>1500ms) to reduce log volume
/// </summary> /// </summary>
public void LogQueryExecution(string query, TimeSpan executionTime, int? rowsAffected = null, Exception? exception = null) public void LogQueryExecution(string query, TimeSpan executionTime, int? rowsAffected = null, Exception? exception = null)
{ {
_executedQueries.Add(query); _executedQueries.Add(query);
var logLevel = exception != null ? LogLevel.Error : const int slowQueryThresholdMs = 1500; // Only log queries slower than this threshold
executionTime.TotalMilliseconds > 1000 ? LogLevel.Warning : LogLevel.Information;
var logMessage = exception != null var logMessage = exception != null
? "[SQL-QUERY-ERROR] {OperationId} | {Repository}.{Method} | Query failed after {ExecutionTime}ms | Error: {Error}" ? "[SQL-QUERY-ERROR] {OperationId} | {Repository}.{Method} | Query failed after {ExecutionTime}ms | Error: {Error}"
@@ -75,23 +76,24 @@ public class SqlQueryLogger : IDisposable
// Send SQL error to Sentry // Send SQL error to Sentry
SendSqlErrorToSentry(query, executionTime, exception, rowsAffected); SendSqlErrorToSentry(query, executionTime, exception, rowsAffected);
} }
else else if (executionTime.TotalMilliseconds > slowQueryThresholdMs)
{ {
_logger.Log(logLevel, logMessage, args); // Only log slow queries, not normal queries
_logger.LogWarning(logMessage, args);
// Send slow query alert to Sentry // Send slow query alert to Sentry
if (executionTime.TotalMilliseconds > 2000) // Critical slow query threshold if (executionTime.TotalMilliseconds > 2000) // Critical slow query threshold
{ {
SendSlowQueryToSentry(query, executionTime, rowsAffected); SendSlowQueryToSentry(query, executionTime, rowsAffected);
} }
else if (executionTime.TotalMilliseconds > 1000) // Warning threshold else
{ {
SendSlowQueryWarningToSentry(query, executionTime, rowsAffected); SendSlowQueryWarningToSentry(query, executionTime, rowsAffected);
} }
} }
// Log query details for slow queries or errors // Log query details for slow queries or errors only
if (executionTime.TotalMilliseconds > 500 || exception != null) if (executionTime.TotalMilliseconds > slowQueryThresholdMs || exception != null)
{ {
_logger.LogWarning( _logger.LogWarning(
"[SQL-QUERY-DETAILS] {OperationId} | Query: {Query} | Parameters: {Parameters}", "[SQL-QUERY-DETAILS] {OperationId} | Query: {Query} | Parameters: {Parameters}",
@@ -127,13 +129,14 @@ public class SqlQueryLogger : IDisposable
args[5] = exception.Message; args[5] = exception.Message;
_logger.LogError(exception, logMessage, args); _logger.LogError(exception, logMessage, args);
} }
else else if (totalTime.TotalMilliseconds > 1500 || _executedQueries.Count > 10)
{ {
// Only log slow operations or operations with many queries
_logger.Log(logLevel, logMessage, args); _logger.Log(logLevel, logMessage, args);
} }
// Log operation summary for long-running operations // Log operation summary for long-running operations or many queries
if (totalTime.TotalMilliseconds > 1000 || _executedQueries.Count > 5) if (totalTime.TotalMilliseconds > 1500 || _executedQueries.Count > 10)
{ {
_logger.LogWarning( _logger.LogWarning(
"[SQL-OP-SUMMARY] {OperationId} | Parameters: {Parameters} | Query Count: {QueryCount} | Total Time: {TotalTime}ms", "[SQL-OP-SUMMARY] {OperationId} | Parameters: {Parameters} | Query Count: {QueryCount} | Total Time: {TotalTime}ms",