- 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.
249 lines
6.6 KiB
Markdown
249 lines
6.6 KiB
Markdown
# 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
|
|
|
|
```php
|
|
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:
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// 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 |