🔷 Error Handling in C# TuskLang

C# Documentation

Error Handling in C# TuskLang

Overview

Robust error handling is essential for building reliable and resilient applications. This guide covers exception handling, error recovery, fault tolerance, and error handling best practices for C# TuskLang applications.

🚨 Exception Handling

Global Exception Handler

public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;
    private readonly TSKConfig _config;
    private readonly IMetricsService _metricsService;
    private readonly IAlertService _alertService;
    
    public GlobalExceptionHandler(
        ILogger<GlobalExceptionHandler> logger,
        TSKConfig config,
        IMetricsService metricsService,
        IAlertService alertService)
    {
        _logger = logger;
        _config = config;
        _metricsService = metricsService;
        _alertService = alertService;
    }
    
    public async Task<ExceptionHandlingResult> HandleAsync(Exception exception, ExceptionContext context)
    {
        try
        {
            // Log the exception
            await LogExceptionAsync(exception, context);
            
            // Record metrics
            await RecordExceptionMetricsAsync(exception, context);
            
            // Check if alert should be triggered
            await CheckAndTriggerAlertAsync(exception, context);
            
            // Determine handling strategy
            var strategy = DetermineHandlingStrategy(exception, context);
            
            // Execute handling strategy
            var result = await ExecuteHandlingStrategyAsync(strategy, exception, context);
            
            return result;
        }
        catch (Exception handlerException)
        {
            _logger.LogError(handlerException, "Exception handler failed while handling {ExceptionType}", 
                exception.GetType().Name);
            
            return ExceptionHandlingResult.Failure("Exception handler failed", handlerException);
        }
    }
    
    private async Task LogExceptionAsync(Exception exception, ExceptionContext context)
    {
        var logLevel = DetermineLogLevel(exception);
        var message = FormatExceptionMessage(exception, context);
        
        _logger.Log(logLevel, exception, message);
        
        // Store exception in database for analysis
        await StoreExceptionAsync(exception, context);
    }
    
    private LogLevel DetermineLogLevel(Exception exception)
    {
        return exception switch
        {
            ValidationException => LogLevel.Warning,
            NotFoundException => LogLevel.Information,
            UnauthorizedAccessException => LogLevel.Warning,
            TimeoutException => LogLevel.Warning,
            _ => LogLevel.Error
        };
    }
    
    private string FormatExceptionMessage(Exception exception, ExceptionContext context)
    {
        var message = $"Exception in {context.OperationName}";
        
        if (!string.IsNullOrEmpty(context.UserId))
        {
            message += $" for user {context.UserId}";
        }
        
        if (!string.IsNullOrEmpty(context.CorrelationId))
        {
            message += $" (Correlation: {context.CorrelationId})";
        }
        
        message += $": {exception.Message}";
        
        return message;
    }
    
    private async Task StoreExceptionAsync(Exception exception, ExceptionContext context)
    {
        try
        {
            var exceptionRecord = new ExceptionRecord
            {
                Id = Guid.NewGuid(),
                ExceptionType = exception.GetType().Name,
                Message = exception.Message,
                StackTrace = exception.StackTrace,
                OperationName = context.OperationName,
                UserId = context.UserId,
                CorrelationId = context.CorrelationId,
                Timestamp = DateTime.UtcNow,
                Severity = DetermineSeverity(exception),
                Context = JsonSerializer.Serialize(context.AdditionalContext)
            };
            
            // Store in database
            await StoreExceptionRecordAsync(exceptionRecord);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to store exception record");
        }
    }
    
    private ExceptionSeverity DetermineSeverity(Exception exception)
    {
        return exception switch
        {
            ValidationException => ExceptionSeverity.Low,
            NotFoundException => ExceptionSeverity.Low,
            UnauthorizedAccessException => ExceptionSeverity.Medium,
            TimeoutException => ExceptionSeverity.Medium,
            DatabaseException => ExceptionSeverity.High,
            _ => ExceptionSeverity.High
        };
    }
    
    private async Task RecordExceptionMetricsAsync(Exception exception, ExceptionContext context)
    {
        try
        {
            var tags = new Dictionary<string, object>
            {
                ["exception_type"] = exception.GetType().Name,
                ["operation"] = context.OperationName,
                ["severity"] = DetermineSeverity(exception).ToString()
            };
            
            _metricsService.IncrementCounter("exceptions_total", 1, tags);
            
            if (exception is TimeoutException)
            {
                _metricsService.IncrementCounter("timeouts_total", 1, tags);
            }
            else if (exception is DatabaseException)
            {
                _metricsService.IncrementCounter("database_errors_total", 1, tags);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to record exception metrics");
        }
    }
    
    private async Task CheckAndTriggerAlertAsync(Exception exception, ExceptionContext context)
    {
        try
        {
            var severity = DetermineSeverity(exception);
            
            if (severity >= ExceptionSeverity.High)
            {
                var alertMessage = $"High severity exception: {exception.GetType().Name} in {context.OperationName}";
                
                await _alertService.TriggerAlertAsync(
                    "high_severity_exception",
                    alertMessage,
                    new Dictionary<string, object>
                    {
                        ["exception_type"] = exception.GetType().Name,
                        ["operation"] = context.OperationName,
                        ["correlation_id"] = context.CorrelationId ?? "unknown"
                    });
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to trigger alert for exception");
        }
    }
    
    private ExceptionHandlingStrategy DetermineHandlingStrategy(Exception exception, ExceptionContext context)
    {
        return exception switch
        {
            ValidationException => ExceptionHandlingStrategy.ReturnError,
            NotFoundException => ExceptionHandlingStrategy.ReturnError,
            UnauthorizedAccessException => ExceptionHandlingStrategy.ReturnError,
            TimeoutException => ExceptionHandlingStrategy.Retry,
            DatabaseException => ExceptionHandlingStrategy.Retry,
            _ => ExceptionHandlingStrategy.ReturnError
        };
    }
    
    private async Task<ExceptionHandlingResult> ExecuteHandlingStrategyAsync(
        ExceptionHandlingStrategy strategy,
        Exception exception,
        ExceptionContext context)
    {
        return strategy switch
        {
            ExceptionHandlingStrategy.ReturnError => await HandleReturnErrorAsync(exception, context),
            ExceptionHandlingStrategy.Retry => await HandleRetryAsync(exception, context),
            ExceptionHandlingStrategy.CircuitBreaker => await HandleCircuitBreakerAsync(exception, context),
            _ => ExceptionHandlingResult.Failure("Unknown handling strategy", exception)
        };
    }
    
    private async Task<ExceptionHandlingResult> HandleReturnErrorAsync(Exception exception, ExceptionContext context)
    {
        var errorResponse = new ErrorResponse
        {
            ErrorCode = GetErrorCode(exception),
            Message = GetUserFriendlyMessage(exception),
            CorrelationId = context.CorrelationId,
            Timestamp = DateTime.UtcNow
        };
        
        return ExceptionHandlingResult.Success(errorResponse);
    }
    
    private async Task<ExceptionHandlingResult> HandleRetryAsync(Exception exception, ExceptionContext context)
    {
        // This would typically involve retry logic
        // For now, return error response
        return await HandleReturnErrorAsync(exception, context);
    }
    
    private async Task<ExceptionHandlingResult> HandleCircuitBreakerAsync(Exception exception, ExceptionContext context)
    {
        // This would typically involve circuit breaker logic
        // For now, return error response
        return await HandleReturnErrorAsync(exception, context);
    }
    
    private string GetErrorCode(Exception exception)
    {
        return exception switch
        {
            ValidationException => "VALIDATION_ERROR",
            NotFoundException => "NOT_FOUND",
            UnauthorizedAccessException => "UNAUTHORIZED",
            TimeoutException => "TIMEOUT",
            DatabaseException => "DATABASE_ERROR",
            _ => "INTERNAL_ERROR"
        };
    }
    
    private string GetUserFriendlyMessage(Exception exception)
    {
        return exception switch
        {
            ValidationException => "The provided data is invalid. Please check your input and try again.",
            NotFoundException => "The requested resource was not found.",
            UnauthorizedAccessException => "You are not authorized to perform this action.",
            TimeoutException => "The operation timed out. Please try again.",
            DatabaseException => "A database error occurred. Please try again later.",
            _ => "An unexpected error occurred. Please try again later."
        };
    }
    
    private async Task StoreExceptionRecordAsync(ExceptionRecord record)
    {
        // Implementation would store in database
        await Task.CompletedTask;
    }
}

public interface IExceptionHandler { Task<ExceptionHandlingResult> HandleAsync(Exception exception, ExceptionContext context); }

public class ExceptionContext { public string OperationName { get; set; } = string.Empty; public string? UserId { get; set; } public string? CorrelationId { get; set; } public Dictionary<string, object> AdditionalContext { get; set; } = new(); }

public class ExceptionHandlingResult { public bool Success { get; set; } public object? Result { get; set; } public string? ErrorMessage { get; set; } public Exception? Exception { get; set; } public static ExceptionHandlingResult Success(object? result = null) { return new ExceptionHandlingResult { Success = true, Result = result }; } public static ExceptionHandlingResult Failure(string errorMessage, Exception? exception = null) { return new ExceptionHandlingResult { Success = false, ErrorMessage = errorMessage, Exception = exception }; } }

public enum ExceptionHandlingStrategy { ReturnError, Retry, CircuitBreaker }

public enum ExceptionSeverity { Low, Medium, High, Critical }

public class ExceptionRecord { public Guid Id { get; set; } public string ExceptionType { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; public string? StackTrace { get; set; } public string OperationName { get; set; } = string.Empty; public string? UserId { get; set; } public string? CorrelationId { get; set; } public DateTime Timestamp { get; set; } public ExceptionSeverity Severity { get; set; } public string Context { get; set; } = string.Empty; }

public class ErrorResponse { public string ErrorCode { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; public string? CorrelationId { get; set; } public DateTime Timestamp { get; set; } }

Custom Exceptions

public class ValidationException : Exception
{
    public List<ValidationError> Errors { get; }
    
    public ValidationException(string message, List<ValidationError> errors) : base(message)
    {
        Errors = errors;
    }
    
    public ValidationException(string message) : base(message)
    {
        Errors = new List<ValidationError>();
    }
}

public class ValidationError { public string Field { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; public string Code { get; set; } = string.Empty; }

public class NotFoundException : Exception { public string ResourceType { get; } public object ResourceId { get; } public NotFoundException(string resourceType, object resourceId) : base($"{resourceType} with id {resourceId} was not found") { ResourceType = resourceType; ResourceId = resourceId; } }

public class DatabaseException : Exception { public string Operation { get; } public string? Sql { get; } public DatabaseException(string operation, string message, Exception? innerException = null) : base(message, innerException) { Operation = operation; } public DatabaseException(string operation, string message, string sql, Exception? innerException = null) : base(message, innerException) { Operation = operation; Sql = sql; } }

public class ConfigurationException : Exception { public string ConfigurationKey { get; } public ConfigurationException(string configurationKey, string message) : base(message) { ConfigurationKey = configurationKey; } }

public class ServiceUnavailableException : Exception { public string ServiceName { get; } public TimeSpan RetryAfter { get; } public ServiceUnavailableException(string serviceName, TimeSpan retryAfter) : base($"Service {serviceName} is currently unavailable. Retry after {retryAfter.TotalSeconds} seconds") { ServiceName = serviceName; RetryAfter = retryAfter; } }

🔄 Error Recovery

Retry Policy

public class RetryPolicy
{
    private readonly ILogger<RetryPolicy> _logger;
    private readonly TSKConfig _config;
    private readonly IMetricsService _metricsService;
    
    public RetryPolicy(ILogger<RetryPolicy> logger, TSKConfig config, IMetricsService metricsService)
    {
        _logger = logger;
        _config = config;
        _metricsService = metricsService;
    }
    
    public async Task<T> ExecuteAsync<T>(
        Func<Task<T>> operation,
        RetryOptions options,
        CancellationToken cancellationToken = default)
    {
        var attempt = 0;
        var lastException = (Exception?)null;
        
        while (attempt < options.MaxRetries)
        {
            try
            {
                attempt++;
                
                _logger.LogDebug("Executing operation (attempt {Attempt}/{MaxRetries})", 
                    attempt, options.MaxRetries);
                
                var result = await operation();
                
                if (attempt > 1)
                {
                    _logger.LogInformation("Operation succeeded on attempt {Attempt}", attempt);
                    _metricsService.IncrementCounter("retry_success", 1, new Dictionary<string, object>
                    {
                        ["attempt"] = attempt
                    });
                }
                
                return result;
            }
            catch (Exception ex) when (ShouldRetry(ex, options) && attempt < options.MaxRetries)
            {
                lastException = ex;
                
                var delay = CalculateDelay(attempt, options);
                
                _logger.LogWarning(ex, "Operation failed on attempt {Attempt}/{MaxRetries}. Retrying in {Delay}ms", 
                    attempt, options.MaxRetries, delay.TotalMilliseconds);
                
                _metricsService.IncrementCounter("retry_attempt", 1, new Dictionary<string, object>
                {
                    ["attempt"] = attempt,
                    ["exception_type"] = ex.GetType().Name
                });
                
                await Task.Delay(delay, cancellationToken);
            }
        }
        
        _logger.LogError(lastException, "Operation failed after {MaxRetries} attempts", options.MaxRetries);
        _metricsService.IncrementCounter("retry_failure", 1);
        
        throw lastException!;
    }
    
    private bool ShouldRetry(Exception exception, RetryOptions options)
    {
        if (options.RetryableExceptions.Contains(exception.GetType()))
        {
            return true;
        }
        
        if (exception is HttpRequestException httpEx)
        {
            return options.RetryableHttpStatusCodes.Contains(httpEx.StatusCode);
        }
        
        return false;
    }
    
    private TimeSpan CalculateDelay(int attempt, RetryOptions options)
    {
        return options.BackoffStrategy switch
        {
            BackoffStrategy.Fixed => options.BaseDelay,
            BackoffStrategy.Exponential => TimeSpan.FromMilliseconds(
                options.BaseDelay.TotalMilliseconds * Math.Pow(2, attempt - 1)),
            BackoffStrategy.Linear => TimeSpan.FromMilliseconds(
                options.BaseDelay.TotalMilliseconds * attempt),
            _ => options.BaseDelay
        };
    }
}

public class RetryOptions { public int MaxRetries { get; set; } = 3; public TimeSpan BaseDelay { get; set; } = TimeSpan.FromSeconds(1); public BackoffStrategy BackoffStrategy { get; set; } = BackoffStrategy.Exponential; public List<Type> RetryableExceptions { get; set; } = new() { typeof(TimeoutException), typeof(HttpRequestException), typeof(DatabaseException) }; public List<HttpStatusCode> RetryableHttpStatusCodes { get; set; } = new() { HttpStatusCode.RequestTimeout, HttpStatusCode.InternalServerError, HttpStatusCode.BadGateway, HttpStatusCode.ServiceUnavailable, HttpStatusCode.GatewayTimeout }; }

public enum BackoffStrategy { Fixed, Exponential, Linear }

Circuit Breaker

public class CircuitBreaker
{
    private readonly ILogger<CircuitBreaker> _logger;
    private readonly TSKConfig _config;
    private readonly IMetricsService _metricsService;
    private readonly string _name;
    private readonly object _lock = new();
    
    private CircuitBreakerState _state = CircuitBreakerState.Closed;
    private int _failureCount = 0;
    private DateTime _lastFailureTime = DateTime.MinValue;
    private DateTime _lastStateChangeTime = DateTime.UtcNow;
    
    public CircuitBreaker(
        ILogger<CircuitBreaker> logger,
        TSKConfig config,
        IMetricsService metricsService,
        string name)
    {
        _logger = logger;
        _config = config;
        _metricsService = metricsService;
        _name = name;
    }
    
    public async Task<T> ExecuteAsync<T>(
        Func<Task<T>> operation,
        CancellationToken cancellationToken = default)
    {
        if (!await CanExecuteAsync())
        {
            throw new CircuitBreakerOpenException(_name);
        }
        
        try
        {
            var result = await operation();
            await OnSuccessAsync();
            return result;
        }
        catch (Exception ex)
        {
            await OnFailureAsync(ex);
            throw;
        }
    }
    
    private async Task<bool> CanExecuteAsync()
    {
        lock (_lock)
        {
            switch (_state)
            {
                case CircuitBreakerState.Closed:
                    return true;
                
                case CircuitBreakerState.Open:
                    var timeout = TimeSpan.FromSeconds(_config.Get<int>($"circuit_breaker.{_name}.timeout_seconds", 60));
                    if (DateTime.UtcNow - _lastFailureTime >= timeout)
                    {
                        _state = CircuitBreakerState.HalfOpen;
                        _lastStateChangeTime = DateTime.UtcNow;
                        _logger.LogInformation("Circuit breaker {Name} moved to half-open state", _name);
                    }
                    return false;
                
                case CircuitBreakerState.HalfOpen:
                    return true;
                
                default:
                    return false;
            }
        }
    }
    
    private async Task OnSuccessAsync()
    {
        lock (_lock)
        {
            if (_state == CircuitBreakerState.HalfOpen)
            {
                _state = CircuitBreakerState.Closed;
                _failureCount = 0;
                _lastStateChangeTime = DateTime.UtcNow;
                _logger.LogInformation("Circuit breaker {Name} moved to closed state", _name);
            }
        }
        
        _metricsService.IncrementCounter("circuit_breaker_success", 1, new Dictionary<string, object>
        {
            ["circuit_breaker"] = _name
        });
    }
    
    private async Task OnFailureAsync(Exception exception)
    {
        lock (_lock)
        {
            _failureCount++;
            _lastFailureTime = DateTime.UtcNow;
            
            var failureThreshold = _config.Get<int>($"circuit_breaker.{_name}.failure_threshold", 5);
            
            if (_state == CircuitBreakerState.Closed && _failureCount >= failureThreshold)
            {
                _state = CircuitBreakerState.Open;
                _lastStateChangeTime = DateTime.UtcNow;
                _logger.LogWarning("Circuit breaker {Name} moved to open state after {FailureCount} failures", 
                    _name, _failureCount);
            }
            else if (_state == CircuitBreakerState.HalfOpen)
            {
                _state = CircuitBreakerState.Open;
                _lastStateChangeTime = DateTime.UtcNow;
                _logger.LogWarning("Circuit breaker {Name} moved back to open state", _name);
            }
        }
        
        _metricsService.IncrementCounter("circuit_breaker_failure", 1, new Dictionary<string, object>
        {
            ["circuit_breaker"] = _name,
            ["exception_type"] = exception.GetType().Name
        });
    }
    
    public CircuitBreakerStatus GetStatus()
    {
        lock (_lock)
        {
            return new CircuitBreakerStatus
            {
                Name = _name,
                State = _state,
                FailureCount = _failureCount,
                LastFailureTime = _lastFailureTime,
                LastStateChangeTime = _lastStateChangeTime
            };
        }
    }
    
    public void Reset()
    {
        lock (_lock)
        {
            _state = CircuitBreakerState.Closed;
            _failureCount = 0;
            _lastStateChangeTime = DateTime.UtcNow;
            _logger.LogInformation("Circuit breaker {Name} manually reset", _name);
        }
    }
}

public enum CircuitBreakerState { Closed, Open, HalfOpen }

public class CircuitBreakerStatus { public string Name { get; set; } = string.Empty; public CircuitBreakerState State { get; set; } public int FailureCount { get; set; } public DateTime LastFailureTime { get; set; } public DateTime LastStateChangeTime { get; set; } }

public class CircuitBreakerOpenException : Exception { public string CircuitBreakerName { get; } public CircuitBreakerOpenException(string circuitBreakerName) : base($"Circuit breaker '{circuitBreakerName}' is open") { CircuitBreakerName = circuitBreakerName; } }

🛡️ Fault Tolerance

Fault Tolerant Service

public class FaultTolerantService
{
    private readonly ILogger<FaultTolerantService> _logger;
    private readonly TSKConfig _config;
    private readonly RetryPolicy _retryPolicy;
    private readonly CircuitBreaker _circuitBreaker;
    private readonly IExceptionHandler _exceptionHandler;
    
    public FaultTolerantService(
        ILogger<FaultTolerantService> logger,
        TSKConfig config,
        RetryPolicy retryPolicy,
        CircuitBreaker circuitBreaker,
        IExceptionHandler exceptionHandler)
    {
        _logger = logger;
        _config = config;
        _retryPolicy = retryPolicy;
        _circuitBreaker = circuitBreaker;
        _exceptionHandler = exceptionHandler;
    }
    
    public async Task<T> ExecuteWithFaultToleranceAsync<T>(
        Func<Task<T>> operation,
        string operationName,
        FaultToleranceOptions options,
        CancellationToken cancellationToken = default)
    {
        var context = new ExceptionContext
        {
            OperationName = operationName,
            CorrelationId = Guid.NewGuid().ToString()
        };
        
        try
        {
            // Execute with circuit breaker
            var result = await _circuitBreaker.ExecuteAsync(async () =>
            {
                // Execute with retry policy
                return await _retryPolicy.ExecuteAsync(operation, options.RetryOptions, cancellationToken);
            }, cancellationToken);
            
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Operation {OperationName} failed with fault tolerance", operationName);
            
            // Handle exception
            var handlingResult = await _exceptionHandler.HandleAsync(ex, context);
            
            if (handlingResult.Success && handlingResult.Result is T result)
            {
                return result;
            }
            
            throw;
        }
    }
    
    public async Task<T> ExecuteWithFallbackAsync<T>(
        Func<Task<T>> primaryOperation,
        Func<Task<T>> fallbackOperation,
        string operationName,
        FaultToleranceOptions options,
        CancellationToken cancellationToken = default)
    {
        try
        {
            return await ExecuteWithFaultToleranceAsync(primaryOperation, operationName, options, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Primary operation {OperationName} failed, trying fallback", operationName);
            
            try
            {
                return await ExecuteWithFaultToleranceAsync(fallbackOperation, $"{operationName}_fallback", options, cancellationToken);
            }
            catch (Exception fallbackEx)
            {
                _logger.LogError(fallbackEx, "Fallback operation {OperationName} also failed", operationName);
                throw;
            }
        }
    }
    
    public async Task<T> ExecuteWithTimeoutAsync<T>(
        Func<Task<T>> operation,
        TimeSpan timeout,
        string operationName,
        CancellationToken cancellationToken = default)
    {
        using var timeoutCts = new CancellationTokenSource(timeout);
        using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
            timeoutCts.Token, cancellationToken);
        
        try
        {
            return await operation().WaitAsync(combinedCts.Token);
        }
        catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
        {
            var timeoutException = new TimeoutException($"Operation {operationName} timed out after {timeout.TotalSeconds} seconds");
            
            var context = new ExceptionContext
            {
                OperationName = operationName,
                CorrelationId = Guid.NewGuid().ToString()
            };
            
            var handlingResult = await _exceptionHandler.HandleAsync(timeoutException, context);
            
            if (handlingResult.Success && handlingResult.Result is T result)
            {
                return result;
            }
            
            throw timeoutException;
        }
    }
}

public class FaultToleranceOptions { public RetryOptions RetryOptions { get; set; } = new(); public TimeSpan? Timeout { get; set; } public bool EnableCircuitBreaker { get; set; } = true; public bool EnableFallback { get; set; } = false; }

📝 Summary

This guide covered comprehensive error handling strategies for C# TuskLang applications:

- Exception Handling: Global exception handler with logging, metrics, and alerting - Custom Exceptions: Domain-specific exceptions for better error categorization - Error Recovery: Retry policies and circuit breakers for fault tolerance - Fault Tolerance: Comprehensive fault tolerance patterns with fallbacks and timeouts

These error handling strategies ensure your C# TuskLang applications are resilient, reliable, and provide excellent user experience even when errors occur.