Files
michaelschiemer/docs/claude/error-handling.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

6.6 KiB

Error Handling & Debugging

This guide covers error handling patterns and debugging strategies in the framework.

Exception Handling

All custom exceptions in the framework must extend FrameworkException to ensure consistent error handling, logging, and recovery mechanisms.

The FrameworkException System

The framework provides a sophisticated exception system with:

  • ExceptionContext: Rich context information for debugging
  • ErrorCode: Categorized error codes with recovery hints
  • RetryAfter: Support for recoverable operations
  • Fluent Interface: Easy context building

Creating Custom Exceptions

namespace App\Domain\User\Exceptions;

use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\ExceptionContext;

final class UserNotFoundException extends FrameworkException
{
    public static function byId(UserId $id): self
    {
        return self::create(
            DatabaseErrorCode::ENTITY_NOT_FOUND,
            "User with ID '{$id->toString()}' not found"
        )->withData([
            'user_id' => $id->toString(),
            'search_type' => 'by_id'
        ]);
    }

    public static function byEmail(Email $email): self
    {
        $context = ExceptionContext::forOperation('user.lookup', 'UserRepository')
            ->withData(['email' => $email->getMasked()]);

        return self::fromContext(
            "User with email not found",
            $context,
            DatabaseErrorCode::ENTITY_NOT_FOUND
        );
    }
}

Using ErrorCode Enums

The framework provides category-specific error code enums for better organization and type safety:

use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\Core\SecurityErrorCode;
use App\Framework\Exception\Core\ValidationErrorCode;

// Database errors
DatabaseErrorCode::CONNECTION_FAILED
DatabaseErrorCode::QUERY_FAILED
DatabaseErrorCode::TRANSACTION_FAILED
DatabaseErrorCode::CONSTRAINT_VIOLATION

// Authentication errors
AuthErrorCode::CREDENTIALS_INVALID
AuthErrorCode::TOKEN_EXPIRED
AuthErrorCode::SESSION_EXPIRED
AuthErrorCode::ACCOUNT_LOCKED

// HTTP errors
HttpErrorCode::BAD_REQUEST
HttpErrorCode::NOT_FOUND
HttpErrorCode::METHOD_NOT_ALLOWED
HttpErrorCode::RATE_LIMIT_EXCEEDED

// Security errors
SecurityErrorCode::CSRF_TOKEN_INVALID
SecurityErrorCode::SQL_INJECTION_DETECTED
SecurityErrorCode::XSS_DETECTED
SecurityErrorCode::PATH_TRAVERSAL_DETECTED

// Validation errors
ValidationErrorCode::INVALID_INPUT
ValidationErrorCode::REQUIRED_FIELD_MISSING
ValidationErrorCode::BUSINESS_RULE_VIOLATION
ValidationErrorCode::INVALID_FORMAT

// Using error codes in exceptions:
throw FrameworkException::create(
    DatabaseErrorCode::QUERY_FAILED,
    "Failed to execute user query"
)->withContext(
    ExceptionContext::forOperation('user.find', 'UserRepository')
        ->withData(['query' => 'SELECT * FROM users WHERE id = ?'])
        ->withDebug(['bind_params' => [$userId]])
);

Exception Context Building

// Method 1: Using factory methods
$exception = FrameworkException::forOperation(
    'payment.process',
    'PaymentService',
    'Payment processing failed',
    HttpErrorCode::BAD_GATEWAY
)->withData([
    'amount' => $amount->toArray(),
    'gateway' => 'stripe',
    'customer_id' => $customerId
])->withMetadata([
    'attempt' => 1,
    'idempotency_key' => $idempotencyKey
]);

// Method 2: Building context separately
$context = ExceptionContext::empty()
    ->withOperation('order.validate', 'OrderService')
    ->withData([
        'order_id' => $orderId,
        'total' => $total->toDecimal()
    ])
    ->withDebug([
        'validation_rules' => ['min_amount', 'max_items'],
        'failed_rule' => 'min_amount'
    ]);

throw FrameworkException::fromContext(
    'Order validation failed',
    $context,
    ValidationErrorCode::BUSINESS_RULE_VIOLATION
);

Recoverable Exceptions

// Creating recoverable exceptions with retry hints
final class RateLimitException extends FrameworkException
{
    public static function exceeded(int $retryAfter): self
    {
        return self::create(
            HttpErrorCode::RATE_LIMIT_EXCEEDED,
            'API rate limit exceeded'
        )->withRetryAfter($retryAfter)
         ->withData(['retry_after_seconds' => $retryAfter]);
    }
}

// Using in code
try {
    $response = $apiClient->request($endpoint);
} catch (RateLimitException $e) {
    if ($e->isRecoverable()) {
        $waitTime = $e->getRetryAfter();
        // Schedule retry after $waitTime seconds
    }
    throw $e;
}

Exception Categories

// Check exception category for handling strategies
try {
    $result = $operation->execute();
} catch (FrameworkException $e) {
    if ($e->isCategory('AUTH')) {
        // Handle authentication errors
        return $this->redirectToLogin();
    }

    if ($e->isCategory('VAL')) {
        // Handle validation errors
        return $this->validationErrorResponse($e);
    }

    if ($e->isErrorCode(DatabaseErrorCode::CONNECTION_FAILED)) {
        // Handle specific database connection errors
        $this->notifyOps($e);
    }

    throw $e;
}

Simple Exceptions for Quick Use

// When you don't need the full context system
throw FrameworkException::simple('Quick error message');

// With previous exception
} catch (\PDOException $e) {
    throw FrameworkException::simple(
        'Database operation failed',
        $e,
        500
    );
}

Exception Data Sanitization

The framework automatically sanitizes sensitive data in exceptions:

// Sensitive keys are automatically redacted
$exception->withData([
    'username' => 'john@example.com',
    'password' => 'secret123',  // Will be logged as '[REDACTED]'
    'api_key' => 'sk_live_...' // Will be logged as '[REDACTED]'
]);

Best Practices

  1. Always extend FrameworkException for custom exceptions
  2. Use ErrorCode enum for categorizable errors
  3. Provide rich context with operation, component, and data
  4. Use factory methods for consistent exception creation
  5. Sanitize sensitive data (automatic for common keys)
  6. Make exceptions domain-specific (UserNotFoundException vs generic NotFoundException)
  7. Include recovery hints for recoverable errors

Logging Best Practices

TODO: Document logging patterns and levels

Debug Strategies

TODO: Document debugging approaches and tools

Error Recovery Patterns

TODO: Document error recovery and graceful degradation

Common Error Scenarios

TODO: List common errors and solutions