354 lines
14 KiB
C#
Executable File
354 lines
14 KiB
C#
Executable File
using Microsoft.Extensions.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Analysis.Infrastructure
|
|
{
|
|
/// <summary>
|
|
/// Enhanced error handling utilities for analysis operations
|
|
/// </summary>
|
|
public static class ErrorHandling
|
|
{
|
|
/// <summary>
|
|
/// Executes an operation with retry logic and comprehensive error handling
|
|
/// </summary>
|
|
public static async Task<T> ExecuteWithRetryAsync<T>(
|
|
Func<Task<T>> operation,
|
|
int maxRetries = 3,
|
|
TimeSpan? delay = null,
|
|
ILogger? logger = null,
|
|
CancellationToken cancellationToken = default,
|
|
[CallerMemberName] string callerName = "",
|
|
[CallerFilePath] string callerFilePath = "",
|
|
[CallerLineNumber] int callerLineNumber = 0)
|
|
{
|
|
var actualDelay = delay ?? TimeSpan.FromSeconds(1);
|
|
var exceptions = new List<Exception>();
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
for (int attempt = 0; attempt <= maxRetries; attempt++)
|
|
{
|
|
try
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
logger?.LogDebug("Executing operation {OperationName}, attempt {Attempt}/{MaxRetries}",
|
|
callerName, attempt + 1, maxRetries + 1);
|
|
|
|
var result = await operation();
|
|
|
|
if (attempt > 0)
|
|
{
|
|
logger?.LogInformation("Operation {OperationName} succeeded after {Attempts} attempts in {Duration}ms",
|
|
callerName, attempt + 1, stopwatch.ElapsedMilliseconds);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
logger?.LogWarning("Operation {OperationName} was cancelled after {Attempts} attempts",
|
|
callerName, attempt + 1);
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
exceptions.Add(ex);
|
|
|
|
logger?.LogWarning(ex, "Operation {OperationName} failed on attempt {Attempt}/{MaxRetries} at {Location}",
|
|
callerName, attempt + 1, maxRetries + 1, $"{callerFilePath}:{callerLineNumber}");
|
|
|
|
if (attempt == maxRetries)
|
|
{
|
|
logger?.LogError("Operation {OperationName} failed after {Attempts} attempts in {Duration}ms",
|
|
callerName, attempt + 1, stopwatch.ElapsedMilliseconds);
|
|
|
|
throw new AggregateException($"Operation {callerName} failed after {maxRetries + 1} attempts", exceptions);
|
|
}
|
|
|
|
if (ShouldRetry(ex))
|
|
{
|
|
var nextDelay = CalculateDelay(actualDelay, attempt);
|
|
logger?.LogDebug("Retrying operation {OperationName} in {Delay}ms", callerName, nextDelay.TotalMilliseconds);
|
|
|
|
await Task.Delay(nextDelay, cancellationToken);
|
|
}
|
|
else
|
|
{
|
|
logger?.LogError(ex, "Operation {OperationName} failed with non-retryable exception", callerName);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException("This should never be reached");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes an operation with comprehensive error handling (non-generic version)
|
|
/// </summary>
|
|
public static async Task ExecuteWithRetryAsync(
|
|
Func<Task> operation,
|
|
int maxRetries = 3,
|
|
TimeSpan? delay = null,
|
|
ILogger? logger = null,
|
|
CancellationToken cancellationToken = default,
|
|
[CallerMemberName] string callerName = "",
|
|
[CallerFilePath] string callerFilePath = "",
|
|
[CallerLineNumber] int callerLineNumber = 0)
|
|
{
|
|
await ExecuteWithRetryAsync(async () =>
|
|
{
|
|
await operation();
|
|
return true; // Dummy return value
|
|
}, maxRetries, delay, logger, cancellationToken, callerName, callerFilePath, callerLineNumber);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Safely executes an operation and returns a result with error information
|
|
/// </summary>
|
|
public static async Task<OperationResult<T>> SafeExecuteAsync<T>(
|
|
Func<Task<T>> operation,
|
|
ILogger? logger = null,
|
|
[CallerMemberName] string callerName = "",
|
|
[CallerFilePath] string callerFilePath = "",
|
|
[CallerLineNumber] int callerLineNumber = 0)
|
|
{
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
try
|
|
{
|
|
logger?.LogDebug("Starting safe execution of {OperationName}", callerName);
|
|
|
|
var result = await operation();
|
|
|
|
logger?.LogDebug("Safe execution of {OperationName} completed successfully in {Duration}ms",
|
|
callerName, stopwatch.ElapsedMilliseconds);
|
|
|
|
return OperationResult<T>.Success(result, stopwatch.Elapsed);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger?.LogError(ex, "Safe execution of {OperationName} failed at {Location} after {Duration}ms",
|
|
callerName, $"{callerFilePath}:{callerLineNumber}", stopwatch.ElapsedMilliseconds);
|
|
|
|
return OperationResult<T>.Failure(ex, stopwatch.Elapsed);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a timeout wrapper for operations
|
|
/// </summary>
|
|
public static async Task<T> WithTimeoutAsync<T>(
|
|
Func<CancellationToken, Task<T>> operation,
|
|
TimeSpan timeout,
|
|
ILogger? logger = null,
|
|
[CallerMemberName] string callerName = "")
|
|
{
|
|
using var cts = new CancellationTokenSource(timeout);
|
|
|
|
try
|
|
{
|
|
logger?.LogDebug("Starting operation {OperationName} with timeout {Timeout}ms",
|
|
callerName, timeout.TotalMilliseconds);
|
|
|
|
return await operation(cts.Token);
|
|
}
|
|
catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
|
|
{
|
|
logger?.LogWarning("Operation {OperationName} timed out after {Timeout}ms",
|
|
callerName, timeout.TotalMilliseconds);
|
|
|
|
throw new TimeoutException($"Operation {callerName} timed out after {timeout.TotalMilliseconds}ms");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles exceptions from plugin operations with detailed logging
|
|
/// </summary>
|
|
public static PluginErrorInfo HandlePluginException(
|
|
Exception exception,
|
|
string pluginName,
|
|
string operationName,
|
|
ILogger? logger = null)
|
|
{
|
|
var errorInfo = new PluginErrorInfo
|
|
{
|
|
PluginName = pluginName,
|
|
OperationName = operationName,
|
|
Exception = exception,
|
|
Timestamp = DateTime.UtcNow,
|
|
ErrorType = ClassifyError(exception),
|
|
Severity = DetermineSeverity(exception),
|
|
Recoverable = IsRecoverable(exception)
|
|
};
|
|
|
|
logger?.Log(GetLogLevel(errorInfo.Severity), exception,
|
|
"Plugin {PluginName} failed during {OperationName}: {ErrorType} - {ErrorMessage}",
|
|
pluginName, operationName, errorInfo.ErrorType, exception.Message);
|
|
|
|
return errorInfo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if an exception should trigger a retry
|
|
/// </summary>
|
|
private static bool ShouldRetry(Exception exception)
|
|
{
|
|
return exception switch
|
|
{
|
|
OperationCanceledException => false,
|
|
ArgumentNullException => false,
|
|
ArgumentException => false,
|
|
InvalidOperationException => false,
|
|
NotSupportedException => false,
|
|
UnauthorizedAccessException => false,
|
|
System.IO.FileNotFoundException => false,
|
|
System.IO.DirectoryNotFoundException => false,
|
|
System.IO.IOException => true,
|
|
TimeoutException => true,
|
|
_ => true
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates exponential backoff delay
|
|
/// </summary>
|
|
private static TimeSpan CalculateDelay(TimeSpan baseDelay, int attempt)
|
|
{
|
|
var exponentialDelay = TimeSpan.FromTicks(baseDelay.Ticks * (long)Math.Pow(2, attempt));
|
|
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 100));
|
|
return exponentialDelay + jitter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Classifies the type of error for better handling
|
|
/// </summary>
|
|
private static string ClassifyError(Exception exception)
|
|
{
|
|
return exception switch
|
|
{
|
|
ArgumentException => "Configuration",
|
|
UnauthorizedAccessException => "Security",
|
|
System.IO.IOException => "IO",
|
|
TimeoutException => "Timeout",
|
|
OutOfMemoryException => "Memory",
|
|
StackOverflowException => "Stack",
|
|
OperationCanceledException => "Cancellation",
|
|
_ => "General"
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the severity of an error
|
|
/// </summary>
|
|
private static ErrorSeverity DetermineSeverity(Exception exception)
|
|
{
|
|
return exception switch
|
|
{
|
|
OutOfMemoryException => ErrorSeverity.Critical,
|
|
StackOverflowException => ErrorSeverity.Critical,
|
|
UnauthorizedAccessException => ErrorSeverity.High,
|
|
System.IO.FileNotFoundException => ErrorSeverity.Medium,
|
|
System.IO.DirectoryNotFoundException => ErrorSeverity.Medium,
|
|
ArgumentException => ErrorSeverity.Medium,
|
|
TimeoutException => ErrorSeverity.Low,
|
|
OperationCanceledException => ErrorSeverity.Low,
|
|
_ => ErrorSeverity.Medium
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if an error is recoverable
|
|
/// </summary>
|
|
private static bool IsRecoverable(Exception exception)
|
|
{
|
|
return exception switch
|
|
{
|
|
OutOfMemoryException => false,
|
|
StackOverflowException => false,
|
|
UnauthorizedAccessException => false,
|
|
System.IO.FileNotFoundException => false,
|
|
System.IO.DirectoryNotFoundException => false,
|
|
ArgumentException => false,
|
|
TimeoutException => true,
|
|
System.IO.IOException => true,
|
|
_ => true
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets appropriate log level for error severity
|
|
/// </summary>
|
|
private static LogLevel GetLogLevel(ErrorSeverity severity)
|
|
{
|
|
return severity switch
|
|
{
|
|
ErrorSeverity.Critical => LogLevel.Critical,
|
|
ErrorSeverity.High => LogLevel.Error,
|
|
ErrorSeverity.Medium => LogLevel.Warning,
|
|
ErrorSeverity.Low => LogLevel.Information,
|
|
_ => LogLevel.Warning
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of an operation with error handling
|
|
/// </summary>
|
|
public class OperationResult<T>
|
|
{
|
|
public bool IsSuccess { get; private set; }
|
|
public T? Value { get; private set; }
|
|
public Exception? Exception { get; private set; }
|
|
public TimeSpan Duration { get; private set; }
|
|
public string? ErrorMessage => Exception?.Message;
|
|
|
|
private OperationResult(bool isSuccess, T? value, Exception? exception, TimeSpan duration)
|
|
{
|
|
IsSuccess = isSuccess;
|
|
Value = value;
|
|
Exception = exception;
|
|
Duration = duration;
|
|
}
|
|
|
|
public static OperationResult<T> Success(T value, TimeSpan duration)
|
|
{
|
|
return new OperationResult<T>(true, value, null, duration);
|
|
}
|
|
|
|
public static OperationResult<T> Failure(Exception exception, TimeSpan duration)
|
|
{
|
|
return new OperationResult<T>(false, default, exception, duration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Information about plugin errors
|
|
/// </summary>
|
|
public class PluginErrorInfo
|
|
{
|
|
public string PluginName { get; set; } = string.Empty;
|
|
public string OperationName { get; set; } = string.Empty;
|
|
public Exception? Exception { get; set; }
|
|
public DateTime Timestamp { get; set; }
|
|
public string ErrorType { get; set; } = string.Empty;
|
|
public ErrorSeverity Severity { get; set; }
|
|
public bool Recoverable { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Error severity levels
|
|
/// </summary>
|
|
public enum ErrorSeverity
|
|
{
|
|
Low,
|
|
Medium,
|
|
High,
|
|
Critical
|
|
}
|
|
} |