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
- Fluent Syntax - Combine exception handling with other logging features
- Correlation IDs - Track exceptions across service calls
- Data Masking - Protect sensitive data in exception logs