fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,645 @@
# 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
## Unified Error Kernel Architecture
The framework uses a **context-aware error handling system** centered around the `ErrorKernel` class that automatically detects execution context (CLI vs HTTP) and handles errors accordingly.
### ErrorKernel Overview
**Location**: `src/Framework/ExceptionHandling/ErrorKernel.php`
**Key Features**:
- Automatic context detection (CLI vs HTTP)
- Colored console output for CLI errors
- HTTP Response objects for web errors
- Integration with OWASP Security Event System
- Unified error logging via LogReporter
```php
use App\Framework\ExceptionHandling\ErrorKernel;
final readonly class ErrorKernel
{
public function __construct(
private ErrorRendererFactory $rendererFactory = new ErrorRendererFactory,
private ?ExecutionContext $executionContext = null,
private ?ConsoleOutput $consoleOutput = null
) {}
/**
* Context-aware exception handler
* - CLI: Colored console output
* - HTTP: Logs error (middleware creates response)
*/
public function handle(Throwable $e, array $context = []): mixed
{
// Automatic logging
$log = new LogReporter();
$log->report($e);
// Context-aware handling
$executionContext = $this->executionContext ?? ExecutionContext::detect();
if ($executionContext->isCli()) {
$this->handleCliException($e);
return null;
}
// HTTP context - middleware will create response
return null;
}
/**
* Create HTTP Response from exception (for middleware recovery)
*/
public function createHttpResponse(
Throwable $exception,
?ExceptionContextProvider $contextProvider = null,
bool $isDebugMode = false
): Response {
$renderer = new ResponseErrorRenderer($isDebugMode);
return $renderer->createResponse($exception, $contextProvider);
}
}
```
### CLI Error Handling
**CliErrorHandler** registers global PHP error handlers for CLI context:
```php
use App\Framework\ExceptionHandling\CliErrorHandler;
use App\Framework\Console\ConsoleOutput;
// Registration in AppBootstrapper
$output = new ConsoleOutput();
$cliErrorHandler = new CliErrorHandler($output);
$cliErrorHandler->register();
// Automatic colored output for errors:
// - Red for uncaught exceptions
// - Yellow for warnings
// - Cyan for notices
// - Full stack traces in CLI
```
### HTTP Error Handling
**ExceptionHandlingMiddleware** catches exceptions in HTTP request pipeline:
```php
use App\Framework\Http\Middlewares\ExceptionHandlingMiddleware;
#[MiddlewarePriorityAttribute(MiddlewarePriority::ERROR_HANDLING)]
final readonly class ExceptionHandlingMiddleware implements HttpMiddleware
{
public function __invoke(
MiddlewareContext $context,
Next $next,
RequestStateManager $stateManager
): MiddlewareContext {
try {
return $next($context);
} catch (\Throwable $e) {
// Log exception
$this->logger->error('Unhandled exception in HTTP request', [
'exception' => get_class($e),
'message' => $e->getMessage(),
]);
// Create HTTP Response
$errorKernel = new ErrorKernel();
$response = $errorKernel->createHttpResponse(
$e,
null,
isDebugMode: false
);
return $context->withResponse($response);
}
}
}
```
### OWASP Security Event Integration
Exceptions can trigger OWASP security events for audit logging:
```php
use App\Application\Security\OWASPSecurityEventLogger;
use App\Application\Security\OWASPEventIdentifier;
// Automatic security logging
try {
$this->authenticateUser($credentials);
} catch (AuthenticationException $e) {
// ErrorKernel logs exception
$this->errorKernel->handle($e);
// OWASP event for security audit trail
$this->eventDispatcher->dispatch(
new AuthenticationFailedEvent(
OWASPEventIdentifier::AUTHN_LOGIN_FAILURE,
$credentials->username,
$e->getMessage()
)
);
throw $e;
}
```
### Legacy ErrorHandling Module Removed
**IMPORTANT**: The legacy `ErrorHandling` module (`src/Framework/ErrorHandling/`) has been **completely removed** as of the unified exception architecture migration.
**Migration Path**:
- All error handling now uses `ErrorKernel` and `FrameworkException`
- CLI errors: `CliErrorHandler``ErrorKernel`
- HTTP errors: `ExceptionHandlingMiddleware``ErrorKernel`
- Security events: Direct event dispatch via `EventDispatcher`
**Old Pattern** (removed):
```php
// ❌ Legacy - NO LONGER EXISTS
use App\Framework\ErrorHandling\ErrorHandler;
use App\Framework\ErrorHandling\SecurityEventLogger;
$errorHandler = new ErrorHandler();
$errorHandler->register();
```
**New Pattern** (current):
```php
// ✅ Unified - ErrorKernel
use App\Framework\ExceptionHandling\ErrorKernel;
$errorKernel = new ErrorKernel();
$errorKernel->handle($exception);
```
## Logging Best Practices
### Automatic Exception Logging
All exceptions handled by `ErrorKernel` are automatically logged via `LogReporter`:
```php
// Automatic logging happens in ErrorKernel::handle()
$log = new LogReporter();
$log->report($exception);
// Logs include:
// - Exception class and message
// - Stack trace
// - File and line number
// - Context data
```
### Manual Logging
```php
use App\Framework\Logging\Logger;
// Log exceptions with context
try {
$user = $this->userRepository->find($userId);
} catch (UserNotFoundException $e) {
Logger::error('User lookup failed', [
'user_id' => $userId,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
// Log levels
Logger::debug('Debugging information');
Logger::info('Informational message');
Logger::warning('Warning condition');
Logger::error('Error condition');
Logger::critical('Critical failure');
```
## Debug Strategies
### Development vs Production
**Development** (`APP_DEBUG=true`):
- Full stack traces displayed
- Detailed error messages
- Debug data in responses
- SQL query logging
**Production** (`APP_DEBUG=false`):
- Generic error messages
- Stack traces hidden from users
- Errors logged server-side
- Security-safe responses
### Debugging Tools
```php
// Enable debug mode in ErrorKernel
$errorKernel = new ErrorKernel(
executionContext: ExecutionContext::cli(),
consoleOutput: new ConsoleOutput()
);
// HTTP Response with debug mode
$response = $errorKernel->createHttpResponse(
$exception,
$contextProvider,
isDebugMode: true // Shows stack trace in response
);
```
### Error Context Providers
```php
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
// Attach custom context to exceptions
$contextProvider = new ExceptionContextProvider();
$contextProvider->attachContext($exception, [
'request_id' => $requestId,
'user_id' => $userId,
'operation' => 'payment.process'
]);
$response = $errorKernel->createHttpResponse(
$exception,
$contextProvider,
isDebugMode: false
);
```
## Error Recovery Patterns
### Graceful Degradation
```php
// Try primary service, fallback to secondary
try {
return $this->primaryCache->get($key);
} catch (CacheException $e) {
Logger::warning('Primary cache failed, using fallback', [
'error' => $e->getMessage()
]);
return $this->fallbackCache->get($key);
}
```
### Circuit Breaker Pattern
```php
use App\Framework\Resilience\CircuitBreaker;
$circuitBreaker = new CircuitBreaker(
failureThreshold: 5,
timeout: Duration::fromSeconds(60)
);
try {
return $circuitBreaker->call(function() {
return $this->externalApi->request($endpoint);
});
} catch (CircuitOpenException $e) {
// Circuit is open - use cached response
return $this->cachedResponse;
}
```
### Retry with Exponential Backoff
```php
use App\Framework\Queue\ValueObjects\RetryStrategy;
$retryStrategy = new ExponentialBackoffStrategy(
maxAttempts: 3,
baseDelaySeconds: 60
);
$attempt = 0;
while ($attempt < $retryStrategy->getMaxAttempts()) {
try {
return $this->performOperation();
} catch (TransientException $e) {
$attempt++;
if (!$retryStrategy->shouldRetry($attempt)) {
throw $e;
}
$delay = $retryStrategy->getDelay($attempt);
sleep($delay->toSeconds());
}
}
```
## Common Error Scenarios
### 1. Database Connection Failure
```php
try {
$connection = $this->connectionPool->getConnection();
} catch (ConnectionException $e) {
// Log error
$this->errorKernel->handle($e);
// Return cached data or error response
return $this->getCachedData() ?? $this->errorResponse();
}
```
### 2. Validation Errors
```php
try {
$user = User::create($email, $name);
} catch (ValidationException $e) {
// Return validation errors to user
return new JsonResult([
'errors' => $e->getErrors()
], status: Status::UNPROCESSABLE_ENTITY);
}
```
### 3. Authentication Failures
```php
try {
$user = $this->authenticator->authenticate($credentials);
} catch (AuthenticationException $e) {
// Log security event
$this->eventDispatcher->dispatch(
new AuthenticationFailedEvent($credentials->username)
);
// Return 401 Unauthorized
return new JsonResult([
'error' => 'Invalid credentials'
], status: Status::UNAUTHORIZED);
}
```
### 4. Resource Not Found
```php
try {
$order = $this->orderRepository->find($orderId);
} catch (OrderNotFoundException $e) {
// Return 404 Not Found
return new JsonResult([
'error' => 'Order not found'
], status: Status::NOT_FOUND);
}
```
### 5. Rate Limit Exceeded
```php
try {
$this->rateLimiter->checkLimit($userId);
} catch (RateLimitException $e) {
// Return 429 Too Many Requests with retry hint
return new JsonResult([
'error' => 'Rate limit exceeded',
'retry_after' => $e->getRetryAfter()
], status: Status::TOO_MANY_REQUESTS);
}
```