/

Exception Handling

ByteHide Logger provides specialized methods for handling exceptions: Error and Critical. Both methods can accept an Exception parameter to automatically capture exception details like stack traces, inner exceptions, and error types.

Exception Logging Methods

Error Method

Use Error for exceptions that affect functionality but allow the application to continue:

// Method signature
Log.Error(string message, Exception exception = null)
Log.Error(string message, object context, Exception exception = null)

// Basic exception logging
try
{
    ProcessOrder(orderId);
}
catch (Exception ex)
{
    Log.Error("Failed to process order", ex);
}

// Exception with context
try
{
    ProcessPayment(paymentRequest);
}
catch (PaymentException ex)
{
    Log.Error("Payment processing failed", new { 
        TransactionId = paymentRequest.TransactionId,
        Amount = paymentRequest.Amount 
    }, ex);
}

Critical Method

Use Critical for critical exceptions that may cause application termination:

// Method signature  
Log.Critical(string message, Exception exception = null)

// Critical system failures
try
{
    InitializeDatabase();
}
catch (Exception ex)
{
    Log.Critical("Database initialization failed - application cannot continue", ex);
    Environment.Exit(1);
}

// Critical configuration errors
try
{
    LoadConfiguration();
}
catch (ConfigurationException ex)
{
    Log.Critical("Invalid configuration detected - shutting down", ex);
    throw;
}

Exception Information Captured

When you pass an Exception to Error or Critical methods, ByteHide Logger automatically captures:

  • Exception Type: The full type name of the exception
  • Exception Message: The exception's message
  • Stack Trace: Complete stack trace for debugging
  • Inner Exceptions: Details of any inner exceptions
  • Exception Data: Additional data attached to the exception
try
{
    var result = await CallExternalApiAsync();
}
catch (HttpRequestException ex)
{
    // All exception details are automatically captured
    Log.Error("External API call failed", ex);
    
    // Logged information includes:
    // - Exception type: HttpRequestException
    // - Message: "The request timed out"
    // - Stack trace: Full call stack
    // - Inner exception: SocketException details
}

Common Exception Scenarios

Database Operations

try
{
    var orders = await _database.GetOrdersAsync(customerId);
    return orders;
}
catch (SqlException ex)
{
    Log.Error("Database query failed", new { 
        CustomerId = customerId,
        Query = "GetOrdersAsync" 
    }, ex);
    throw;
}
catch (TimeoutException ex)
{
    Log.Error("Database timeout occurred", new { 
        CustomerId = customerId,
        TimeoutSeconds = 30 
    }, ex);
    throw;
}

API Calls

try
{
    var response = await _httpClient.PostAsync("/api/orders", content);
    return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
    Log.Error("HTTP request failed", new { 
        Endpoint = "/api/orders",
        Method = "POST" 
    }, ex);
    throw;
}
catch (TaskCanceledException ex)
{
    Log.Error("Request timeout", new { 
        Endpoint = "/api/orders",
        TimeoutMs = 5000 
    }, ex);
    throw;
}

File Operations

try
{
    var content = await File.ReadAllTextAsync(filePath);
    return JsonSerializer.Deserialize<Order>(content);
}
catch (FileNotFoundException ex)
{
    Log.Error("Configuration file not found", new { 
        FilePath = filePath 
    }, ex);
    throw;
}
catch (JsonException ex)
{
    Log.Critical("Invalid JSON configuration", new { 
        FilePath = filePath 
    }, ex);
    throw;
}

Business Logic Exceptions

try
{
    ValidateOrder(order);
    await ProcessOrderAsync(order);
}
catch (ValidationException ex)
{
    Log.Error("Order validation failed", new { 
        OrderId = order.Id,
        ValidationErrors = ex.Errors 
    }, ex);
    throw;
}
catch (InsufficientStockException ex)
{
    Log.Error("Insufficient stock for order", new { 
        OrderId = order.Id,
        ProductId = ex.ProductId,
        RequestedQuantity = ex.RequestedQuantity,
        AvailableStock = ex.AvailableStock 
    }, ex);
    throw;
}

Exception Handling Patterns

Catch and Log

public async Task<Order> GetOrderAsync(int orderId)
{
    try
    {
        return await _repository.GetOrderAsync(orderId);
    }
    catch (Exception ex)
    {
        Log.Error("Failed to retrieve order", new { OrderId = orderId }, ex);
        throw; // Re-throw to preserve original exception
    }
}

Catch, Log, and Transform

public async Task<ApiResponse<Order>> GetOrderAsync(int orderId)
{
    try
    {
        var order = await _repository.GetOrderAsync(orderId);
        return ApiResponse.Success(order);
    }
    catch (OrderNotFoundException ex)
    {
        Log.Error("Order not found", new { OrderId = orderId }, ex);
        return ApiResponse.NotFound("Order not found");
    }
    catch (Exception ex)
    {
        Log.Critical("Unexpected error retrieving order", new { OrderId = orderId }, ex);
        return ApiResponse.Error("Internal server error");
    }
}

Critical Failure Handling

public async Task StartApplicationAsync()
{
    try
    {
        await InitializeDatabaseAsync();
        await LoadConfigurationAsync();
        await StartServicesAsync();
    }
    catch (DatabaseException ex)
    {
        Log.Critical("Database initialization failed", ex);
        Environment.Exit(1);
    }
    catch (ConfigurationException ex)
    {
        Log.Critical("Configuration loading failed", ex);
        Environment.Exit(1);
    }
    catch (Exception ex)
    {
        Log.Critical("Application startup failed", ex);
        Environment.Exit(1);
    }
}

ASP.NET Core Integration

Controller Exception Handling

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(int id)
    {
        try
        {
            var order = await _orderService.GetOrderAsync(id);
            return Ok(order);
        }
        catch (OrderNotFoundException ex)
        {
            Log.Error("Order not found", new { 
                OrderId = id,
                RequestId = HttpContext.TraceIdentifier 
            }, ex);
            return NotFound();
        }
        catch (Exception ex)
        {
            Log.Critical("Unexpected error in GetOrder", new { 
                OrderId = id,
                RequestId = HttpContext.TraceIdentifier 
            }, ex);
            return StatusCode(500);
        }
    }
}

Global Exception Handler

public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public GlobalExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            var requestInfo = new
            {
                RequestId = context.TraceIdentifier,
                Path = context.Request.Path,
                Method = context.Request.Method,
                QueryString = context.Request.QueryString.ToString()
            };

            if (ex is BusinessException)
            {
                Log.Error("Business logic error", requestInfo, ex);
                context.Response.StatusCode = 400;
            }
            else
            {
                Log.Critical("Unhandled exception", requestInfo, ex);
                context.Response.StatusCode = 500;
            }

            await context.Response.WriteAsync("An error occurred");
        }
    }
}

Exception Logging with Fluent Syntax

You can combine exception logging with fluent syntax:

try
{
    await ProcessOrderAsync(order);
}
catch (PaymentException ex)
{
    Log.WithCorrelationId("req-123")
       .WithMetadata("paymentMethod", order.PaymentMethod)
       .WithContext("orderId", order.Id)
       .WithTags("payment", "error")
       .Error("Payment processing failed", ex);
}

Best Practices

Exception Handling Best Practices

  • Always pass the Exception: Include the Exception parameter to capture full details
  • Add relevant context: Include business context like IDs, operation names, and state
  • Use appropriate levels: Error for recoverable issues, Critical for system failures
  • Don't swallow exceptions: Log and re-throw or transform appropriately
  • Include correlation IDs: Use correlation IDs for request tracking in distributed systems
  • Avoid sensitive data: Don't log passwords, tokens, or PII in exception context

Common Mistakes to Avoid

// ❌ Don't lose exception details
catch (Exception ex)
{
    Log.Error("Something went wrong"); // Missing exception parameter
    throw new Exception("Operation failed"); // Lost original exception
}

// ✅ Capture full exception details
catch (Exception ex)
{
    Log.Error("Order processing failed", new { OrderId = order.Id }, ex);
    throw; // Preserve original exception
}

// ❌ Don't log and swallow
catch (Exception ex)
{
    Log.Error("Error occurred", ex);
    return null; // Swallowing exception
}

// ✅ Log and handle appropriately
catch (Exception ex)
{
    Log.Error("Error occurred", ex);
    throw; // Or return appropriate error response
}

Next Steps

Previous
Tags