Enhance error handling and logging in BotController, LiveTradingBotGrain, and BotService
- Added specific handling for ServiceUnavailableException in BotController to return user-friendly messages. - Improved logging for Orleans exceptions in both BotController and BotService to provide clearer context during errors. - Implemented verification of position closure status in LiveTradingBotGrain, including timeout handling for closing positions. - Enhanced logging for critical and non-critical operations during bot stop processes to ensure better traceability.
This commit is contained in:
119
src/Managing.Core/Exceptions/OrleansExceptionHelper.cs
Normal file
119
src/Managing.Core/Exceptions/OrleansExceptionHelper.cs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Managing.Core.Exceptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to detect and handle Orleans-specific exceptions
|
||||||
|
/// </summary>
|
||||||
|
public static class OrleansExceptionHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if an exception is an Orleans-specific exception
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsOrleansException(Exception ex)
|
||||||
|
{
|
||||||
|
if (ex == null) return false;
|
||||||
|
|
||||||
|
var exceptionTypeName = ex.GetType().Name;
|
||||||
|
var exceptionMessage = ex.Message ?? string.Empty;
|
||||||
|
|
||||||
|
// Check for Orleans exception type names
|
||||||
|
if (exceptionTypeName.Contains("Orleans", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Orleans-specific exception messages
|
||||||
|
if (exceptionMessage.Contains("Orleans", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("grain", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("silo", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("activation", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("forwarding failed", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("Unable to create local activation", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("Request timeout", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("Deadlock", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check inner exception recursively
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
return IsOrleansException(ex.InnerException);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts an Orleans exception to a user-friendly message
|
||||||
|
/// </summary>
|
||||||
|
public static string GetUserFriendlyMessage(Exception ex, string operation = "operation")
|
||||||
|
{
|
||||||
|
if (!IsOrleansException(ex))
|
||||||
|
{
|
||||||
|
return ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
var exceptionTypeName = ex.GetType().Name;
|
||||||
|
var exceptionMessage = ex.Message ?? string.Empty;
|
||||||
|
|
||||||
|
// Handle timeout exceptions
|
||||||
|
if (ex is TimeoutException || ex is TaskCanceledException ||
|
||||||
|
exceptionMessage.Contains("timeout", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("timed out", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"The {operation} timed out. This may occur when closing positions takes longer than expected. Please try again in a moment.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle deadlock exceptions
|
||||||
|
if (exceptionMessage.Contains("deadlock", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"The {operation} could not complete due to a system conflict. Please try again in a moment.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle activation/grain errors
|
||||||
|
if (exceptionMessage.Contains("activation", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("grain", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
exceptionMessage.Contains("forwarding failed", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"The {operation} encountered a temporary system issue. Please try again in a moment.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic Orleans error
|
||||||
|
return $"The {operation} encountered a temporary system issue. Please try again in a moment. If the problem persists, contact support.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps an async operation with timeout and Orleans exception handling
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<T> ExecuteWithTimeoutAndOrleansHandling<T>(
|
||||||
|
Func<Task<T>> operation,
|
||||||
|
TimeSpan timeout,
|
||||||
|
string operationName,
|
||||||
|
ILogger logger = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(timeout);
|
||||||
|
var task = operation();
|
||||||
|
var timeoutTask = Task.Delay(timeout, cts.Token);
|
||||||
|
|
||||||
|
var completedTask = await Task.WhenAny(task, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
{
|
||||||
|
throw new TimeoutException($"The {operationName} timed out after {timeout.TotalSeconds} seconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await task;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (IsOrleansException(ex) || ex is TimeoutException)
|
||||||
|
{
|
||||||
|
logger?.LogError(ex, "Orleans exception or timeout during {OperationName}: {ExceptionType} - {Message}",
|
||||||
|
operationName, ex.GetType().Name, ex.Message);
|
||||||
|
var userMessage = GetUserFriendlyMessage(ex, operationName);
|
||||||
|
throw new ServiceUnavailableException(userMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user