Home / Programming Languages / Error Handling and Logging Best Practices Across Languages
Programming Languages

Error Handling and Logging Best Practices Across Languages

Master error handling and logging across JavaScript, Python, and C# with custom exception hierarchies, structured logging with correlation IDs, retry pattern...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

!Architecture Overview

Error Handling and Logging Best Practices Across Languages

Introduction

Prerequisites

Requirement Details
Basic setup and tooling Basic setup and tooling

Figure: Code pattern examples for error handling and logging best practices across languages—syntax comparison, idiomatic approaches, performance characteristics, and common pitfalls.

Figure: Best practices implementation for error handling and logging best practices across languages—error handling, testing strategies, maintainability patterns, and documentation standards.

Figure: Production readiness checklist for error handling and logging best practices across languages—logging, monitoring, performance tuning, and security hardening.

Robust error handling and effective logging are critical for production applications. This guide covers exception handling patterns, custom exception hierarchies, structured logging with correlation tracking, and modern error monitoring practices across JavaScript, Python, and C#.

Error Handling Fundamentals

Try-Catch-Finally Structure

JavaScript:

async function fetchUserData(userId) {
```javascript
try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
} catch (error) {
    console.error('Failed to fetch user:', error.message);
    throw error;  // Re-throw for caller to handle
} finally {
    console.log('Fetch attempt completed');
}```
}

Python:

def read_config_file(filename):
```sql
try:
    with open(filename, 'r') as file:
        config = json.load(file)
        return config
except FileNotFoundError:
    print(f"Config file not found: {filename}")
    return {}
except json.JSONDecodeError as e:
    print(f"Invalid JSON in config file: {e}")
    return {}
except Exception as e:
    print(f"Unexpected error reading config: {e}")
    raise
finally:
    print("Config read attempt completed")

**C#:**

```csharp
public async Task<User> GetUserAsync(int userId)
{
```text
HttpResponseMessage response = null;
try
{
    response = await _httpClient.GetAsync($"/api/users/{userId}");
    response.EnsureSuccessStatusCode();
    
    var user = await response.Content.ReadFromJsonAsync<User>();
    return user;
}
catch (HttpRequestException ex)
{
    _logger.LogError(ex, "HTTP error fetching user {UserId}", userId);
    throw;
}
catch (JsonException ex)
{
    _logger.LogError(ex, "JSON parsing error for user {UserId}", userId);
    throw;
}
finally
{
    response?.Dispose();
}```
}

Specific vs. Generic Exception Handling

❌ Bad - Catching all exceptions:

try:
```text
result = risky_operation()```
except:  # Catches everything including KeyboardInterrupt!
```text
print("Something went wrong")

**✅ Good - Specific exception handling:**

```python
try:
```text
result = process_payment(amount)```
except PaymentDeclinedError as e:
```text
notify_user(f"Payment declined: {e.reason}")```
except InsufficientFundsError as e:
```text
notify_user(f"Insufficient funds. Available: {e.available}")```
except PaymentProviderError as e:
```text
log_error(f"Provider error: {e}")
retry_payment(amount)```
except Exception as e:
```text
log_critical(f"Unexpected payment error: {e}")
raise

## Custom Exception Hierarchies

### Python Example





```python
class ApplicationError(Exception):
```python
"""Base exception for application errors."""
def __init__(self, message, code=None, details=None):
    super().__init__(message)
    self.code = code
    self.details = details or {}
    self.timestamp = datetime.utcnow()

class ValidationError(ApplicationError):

"""Raised when data validation fails."""
def __init__(self, field, message, value=None):
    super().__init__(
        message=f"Validation failed for {field}: {message}",
        code="VALIDATION_ERROR",
        details={"field": field, "value": value}
    )
    self.field = field

class ResourceNotFoundError(ApplicationError):

"""Raised when a requested resource doesn't exist."""
def __init__(self, resource_type, resource_id):
    super().__init__(
        message=f"{resource_type} with ID {resource_id} not found",
        code="RESOURCE_NOT_FOUND",
        details={"resource_type": resource_type, "resource_id": resource_id}
    )

class AuthenticationError(ApplicationError):

"""Raised when authentication fails."""
def __init__(self, message="Authentication failed"):
    super().__init__(message, code="AUTH_ERROR")

class DatabaseError(ApplicationError):

"""Raised when database operations fail."""
def __init__(self, operation, original_error):
    super().__init__(
        message=f"Database {operation} failed: {str(original_error)}",
        code="DB_ERROR",
        details={"operation": operation}
    )

Usage

def get_user(user_id):

if not isinstance(user_id, int):
    raise ValidationError("user_id", "Must be an integer", user_id)

try:
    user = db.query(User).filter_by(id=user_id).first()
except SQLAlchemyError as e:
    raise DatabaseError("query", e) from e

if user is None:
    raise ResourceNotFoundError("User", user_id)

return user

Error handling

try:

user = get_user("invalid")```
except ValidationError as e:
```text
return {"error": e.code, "message": str(e), "details": e.details}```
except ResourceNotFoundError as e:
```text
return {"error": e.code, "message": str(e)}, 404```
except DatabaseError as e:
```text
logger.error(f"Database error: {e}", exc_info=True)
return {"error": "INTERNAL_ERROR", "message": "Service unavailable"}, 503

## C# Example

```csharp
// Base exception
public abstract class ApplicationException : Exception
{
```text
public string Code { get; }
public Dictionary<string, object> Details { get; }





protected ApplicationException(
    string message,
    string code,
    Exception innerException = null)
    : base(message, innerException)
{
    Code = code;
    Details = new Dictionary<string, object>();
}```
}

// Specific exceptions
public class ValidationException : ApplicationException
{
```text
public string Field { get; }

public ValidationException(string field, string message, object value = null)
    : base($"Validation failed for {field}: {message}", "VALIDATION_ERROR")
{
    Field = field;
    if (value != null)
        Details["value"] = value;
}```
}

public class ResourceNotFoundException : ApplicationException
{
```sql
public ResourceNotFoundException(string resourceType, object resourceId)
    : base($"{resourceType} with ID {resourceId} not found", "RESOURCE_NOT_FOUND")
{
    Details["resourceType"] = resourceType;
    Details["resourceId"] = resourceId;
}```
}

public class ExternalServiceException : ApplicationException
{
```text
public string ServiceName { get; }
public int? StatusCode { get; }

public ExternalServiceException(
    string serviceName,
    string message,
    int? statusCode = null,
    Exception innerException = null)
    : base($"{serviceName} error: {message}", "EXTERNAL_SERVICE_ERROR", innerException)
{
    ServiceName = serviceName;
    StatusCode = statusCode;
}```
}

// Usage with exception filters (C# 6+)
public async Task<User> GetUserAsync(int userId)
{
```text
try
{
    if (userId <= 0)
        throw new ValidationException("userId", "Must be positive", userId);
    
    var user = await _context.Users.FindAsync(userId);
    
    if (user == null)
        throw new ResourceNotFoundException("User", userId);
    
    return user;
}
catch (DbUpdateException ex) when (ex.InnerException is SqlException sqlEx)
{
    _logger.LogError(ex, "Database error fetching user {UserId}", userId);
    throw new ApplicationException("Database operation failed", "DB_ERROR", ex);
}```
}

TypeScript Example

// Base error class
class ApplicationError extends Error {
```text
constructor(
    message: string,
    public code: string,
    public statusCode: number = 500,
    public details?: Record<string, any>
) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);


}```
}

// Specific errors
class ValidationError extends ApplicationError {
```text
constructor(field: string, message: string, value?: any) {
    super(
        `Validation failed for ${field}: ${message}`,
        'VALIDATION_ERROR',
        400,
        { field, value }
    );
}```
}

class NotFoundError extends ApplicationError {
```sql
constructor(resource: string, id: string | number) {
    super(
        `${resource} with ID ${id} not found`,
        'NOT_FOUND',
        404,
        { resource, id }
    );
}```
}

class UnauthorizedError extends ApplicationError {
```text
constructor(message: string = 'Unauthorized access') {
    super(message, 'UNAUTHORIZED', 401);
}```
}

// Express error handler middleware
function errorHandler(
```yaml
err: Error,
req: express.Request,
res: express.Response,
next: express.NextFunction```
) {
```text
if (err instanceof ApplicationError) {
    return res.status(err.statusCode).json({
        error: err.code,
        message: err.message,
        details: err.details
    });
}

// Unexpected errors
logger.error('Unexpected error:', err);
res.status(500).json({
    error: 'INTERNAL_ERROR',
    message: 'An unexpected error occurred'
});```
}

Retry Patterns

Python Retry with Exponential Backoff

import time
import random
from functools import wraps

def retry_with_backoff(
```text
max_attempts=3,
initial_delay=1,
max_delay=60,
exponential_base=2,
jitter=True,
exceptions=(Exception,)```
):
```python
"""Decorator for retrying functions with exponential backoff."""
def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        delay = initial_delay
        
        for attempt in range(1, max_attempts + 1):
            try:
                return func(*args, **kwargs)
            except exceptions as e:
                if attempt == max_attempts:
                    raise
                
                # Calculate delay with exponential backoff
                delay = min(delay * exponential_base, max_delay)
                
                # Add jitter to prevent thundering herd
                if jitter:
                    delay = delay * (0.5 + random.random())
                
                logger.warning(
                    f"Attempt {attempt}/{max_attempts} failed: {e}. "
                    f"Retrying in {delay:.2f}s..."
                )
                time.sleep(delay)
        
    return wrapper
return decorator

Usage

@retry_with_backoff(max_attempts=5, exceptions=(requests.RequestException,)) def fetch_data(url):

response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()

Async version

Async version

Figure: OneDrive admin – sharing policies, sync settings, and storage reports.

async def async_retry_with_backoff(

coro_func,
max_attempts=3,
initial_delay=1,
exceptions=(Exception,)```
):
```javascript
"""Retry async function with exponential backoff."""
delay = initial_delay





for attempt in range(1, max_attempts + 1):
    try:
        return await coro_func()
    except exceptions as e:
        if attempt == max_attempts:
            raise
        
        delay *= 2
        logger.warning(f"Attempt {attempt} failed: {e}. Retrying in {delay}s...")
        await asyncio.sleep(delay)

## C# with Polly

```csharp
using Polly;
using Polly.Retry;





public class ResilientHttpClient
{
```csharp
private readonly HttpClient _httpClient;
private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy;
private readonly ILogger<ResilientHttpClient> _logger;

public ResilientHttpClient(HttpClient httpClient, ILogger<ResilientHttpClient> logger)
{
    _httpClient = httpClient;
    _logger = logger;
    
    _retryPolicy = Policy
        .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
        .Or<HttpRequestException>()
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt =>
                TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
            onRetry: (outcome, timespan, retryCount, context) =>
            {
                _logger.LogWarning(
                    "Request failed with {StatusCode}. Waiting {Delay}s before retry {Retry}",
                    outcome.Result?.StatusCode,
                    timespan.TotalSeconds,
                    retryCount
                );
            }
        );
}

public async Task<T> GetAsync<T>(string url)
{
    var response = await _retryPolicy.ExecuteAsync(async () =>
        await _httpClient.GetAsync(url)
    );
    
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadFromJsonAsync<T>();
}```
}

Structured Logging

Python with Structlog

import structlog
from contextvars import ContextVar

## Context variables for request tracking
request_id_var: ContextVar[str] = ContextVar('request_id', default='')
user_id_var: ContextVar[str] = ContextVar('user_id', default='')





## Configure structlog
structlog.configure(
```text
processors=[
    structlog.contextvars.merge_contextvars,
    structlog.processors.add_log_level,
    structlog.processors.TimeStamper(fmt="iso"),
    structlog.processors.StackInfoRenderer(),
    structlog.processors.format_exc_info,
    structlog.processors.JSONRenderer()
],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),```
)





logger = structlog.get_logger()

## Middleware to set context
def logging_middleware(request):
```text
request_id = request.headers.get('X-Request-ID', str(uuid.uuid4()))
user_id = getattr(request.user, 'id', 'anonymous')





request_id_var.set(request_id)
user_id_var.set(user_id)

structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
    request_id=request_id,
    user_id=user_id,
    path=request.path,
    method=request.method
)

logger.info("request_started")

try:
    response = process_request(request)
    logger.info("request_completed", status_code=response.status_code)
    return response
except Exception as e:
    logger.error("request_failed", error=str(e), exc_info=True)
    raise

Usage in application code

Usage in application code

Figure: Workspace – published reports, datasets, and app installation dialog.

def create_order(user_id, items):

logger.info("creating_order", item_count=len(items))





try:
    total = calculate_total(items)
    logger.debug("order_total_calculated", total=total)
    
    order = Order.objects.create(user_id=user_id, total=total)
    logger.info("order_created", order_id=order.id, total=total)
    
    return order
except ValidationError as e:
    logger.warning("order_validation_failed", errors=e.details)
    raise
except Exception as e:
    logger.error("order_creation_failed", error=str(e), exc_info=True)
    raise

## C# with Serilog

```csharp
using Serilog;
using Serilog.Context;





// Configure Serilog
Log.Logger = new LoggerConfiguration()
```text
.MinimumLevel.Information()
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "MyApp")
.Enrich.WithMachineName()
.WriteTo.Console(new JsonFormatter())
.WriteTo.File(
    new JsonFormatter(),
    "logs/app-.log",
    rollingInterval: RollingInterval.Day,
    retainedFileCountLimit: 30
)
.CreateLogger();

// Middleware for correlation ID public class CorrelationIdMiddleware {

private readonly RequestDelegate _next;

public async Task InvokeAsync(HttpContext context)
{
    var correlationId = context.Request.Headers["X-Correlation-ID"].FirstOrDefault()
        ?? Guid.NewGuid().ToString();
    
    using (LogContext.PushProperty("CorrelationId", correlationId))
    using (LogContext.PushProperty("UserId", context.User?.Identity?.Name))
    {
        context.Response.Headers.Add("X-Correlation-ID", correlationId);
        
        Log.Information(
            "Request started {Method} {Path}",
            context.Request.Method,
            context.Request.Path
        );
        
        try
        {
            await _next(context);
            
            Log.Information(
                "Request completed {StatusCode}",
                context.Response.StatusCode
            );
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Request failed");
            throw;
        }
    }
}```
}

// Service usage
public class OrderService
{
```csharp
private readonly ILogger<OrderService> _logger;

public async Task<Order> CreateOrderAsync(int userId, List<OrderItem> items)
{
    using (_logger.BeginScope(new Dictionary<string, object>
    {
        ["UserId"] = userId,
        ["ItemCount"] = items.Count
    }))
    {
        _logger.LogInformation("Creating order");
        
        try
        {
            var total = items.Sum(i => i.Price * i.Quantity);
            _logger.LogDebug("Order total calculated: {Total}", total);
            
            var order = new Order { UserId = userId, Total = total };
            await _context.Orders.AddAsync(order);
            await _context.SaveChangesAsync();
            
            _logger.LogInformation(
                "Order created successfully {OrderId}",
                order.Id
            );
            
            return order;
        }
        catch (DbUpdateException ex)
        {
            _logger.LogError(ex, "Database error creating order");
            throw;
        }
    }
}```
}

Production Error Monitoring

Application Insights (Azure)

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;

public class PaymentService
{
```csharp
private readonly TelemetryClient _telemetry;

public async Task ProcessPaymentAsync(Payment payment)
{
    var operation = _telemetry.StartOperation<RequestTelemetry>("ProcessPayment");
    operation.Telemetry.Properties["PaymentMethod"] = payment.Method;
    operation.Telemetry.Properties["Amount"] = payment.Amount.ToString();
    
    try
    {
        await _paymentGateway.ChargeAsync(payment);
        
        _telemetry.TrackEvent("PaymentSucceeded", new Dictionary<string, string>
        {
            ["PaymentId"] = payment.Id.ToString(),
            ["Amount"] = payment.Amount.ToString()
        });
        
        operation.Telemetry.Success = true;
    }
    catch (PaymentDeclinedException ex)
    {
        _telemetry.TrackException(ex, new Dictionary<string, string>
        {
            ["PaymentId"] = payment.Id.ToString(),
            ["DeclineReason"] = ex.Reason
        });
        
        operation.Telemetry.Success = false;
        throw;
    }
    finally
    {
        _telemetry.StopOperation(operation);
    }
}```
}

Sentry (JavaScript)

import * as Sentry from '@sentry/node';

Sentry.init({
```yaml
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
],```
});

// Express error handler
app.use(Sentry.Handlers.errorHandler());

// Manual error tracking
async function processOrder(orderId: string): Promise<void> {
```javascript
const transaction = Sentry.startTransaction({
    op: 'process_order',
    name: 'Process Order',
});

try {
    Sentry.setContext('order', { orderId });
    
    const order = await fetchOrder(orderId);
    await validateOrder(order);
    await chargePayment(order);
    
    Sentry.captureMessage('Order processed successfully', {
        level: 'info',
        tags: { orderId },
    });
} catch (error) {
    Sentry.captureException(error, {
        tags: { orderId },
        extra: { errorType: error.constructor.name },
    });
    throw error;
} finally {
    transaction.finish();
}```
}

Best Practices

  1. Catch specific exceptions - Avoid catching generic Exception unless necessary
  2. Log at appropriate levels - DEBUG for detailed info, INFO for business events, WARN for recoverable issues, ERROR for failures
  3. Include context - Add correlation IDs, user IDs, request IDs to all logs
  4. Don't log sensitive data - Sanitize passwords, tokens, credit cards
  5. Use structured logging - JSON format for easier parsing and analysis
  6. Implement retries - For transient failures with exponential backoff
  7. Monitor errors - Use tools like Sentry, Application Insights, or CloudWatch
  8. Set up alerts - Notify team for critical errors or error rate spikes

Architecture Decision and Tradeoffs

When designing software development solutions with Programming Languages, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/
  • https://learn.microsoft.com/azure/
  • https://learn.microsoft.com/power-platform/
  • https://learn.microsoft.com/microsoft-365/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/training/
  • Sample repositories: https://github.com/microsoft
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • Custom exception hierarchies improve error handling and API responses
  • Structured logging with correlation IDs enables effective troubleshooting
  • Retry patterns with exponential backoff handle transient failures
  • Production monitoring tools aggregate and alert on errors
  • Context-aware logging provides visibility into application behavior

Next Steps

  • Implement distributed tracing with OpenTelemetry
  • Set up log aggregation with ELK stack or Splunk
  • Create error budgets and SLOs for reliability
  • Add circuit breakers for failing services

Additional Resources


Log what matters. Handle what fails.

Discussion