- 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.
1767 lines
54 KiB
Markdown
1767 lines
54 KiB
Markdown
# Error Handling - Unified Architecture Design
|
|
|
|
**Date**: 2025-10-12
|
|
**Status**: Design Phase
|
|
**Previous**: ERROR-HANDLING-AUDIT-REPORT.md
|
|
**Next**: ERROR-HANDLING-MIGRATION-PLAN.md
|
|
|
|
## Executive Summary
|
|
|
|
Dieses Dokument definiert die **Unified Exception Architecture** für das Framework. Das Design konsolidiert 4 separate Error-Module in ein einheitliches System unter `src/Framework/Exception/` mit automatischer Integration aller Komponenten.
|
|
|
|
**Design Goals**:
|
|
- ✅ Single Source of Truth für Error Handling
|
|
- ✅ Automatic integration (Pattern Detection, Reporting, Circuit Breaker)
|
|
- ✅ Backward compatibility mit bestehenden Code
|
|
- ✅ Improved developer experience
|
|
- ✅ Maintained functionality from all modules
|
|
- ✅ Performance optimization (async processing, batching)
|
|
|
|
## 1. New Module Structure
|
|
|
|
### Unified Directory Layout
|
|
|
|
```
|
|
src/Framework/Exception/
|
|
├── Core/ # Core exception infrastructure
|
|
│ ├── FrameworkException.php # Enhanced base exception
|
|
│ ├── ErrorCode.php # Enhanced with severity mapping
|
|
│ ├── ExceptionContext.php # Unified context (Domain + Request + System)
|
|
│ ├── ErrorSeverity.php # Moved from ErrorAggregation
|
|
│ └── ErrorLevel.php # Moved from ErrorHandling
|
|
│
|
|
├── Handler/ # Global error handling
|
|
│ ├── ErrorHandler.php # Integrated error handler
|
|
│ ├── ErrorLogger.php # Structured logging
|
|
│ ├── ResponseFactory.php # HTTP response creation
|
|
│ └── ContextBuilder.php # ExceptionContext builder
|
|
│
|
|
├── Aggregation/ # Pattern detection & alerting
|
|
│ ├── ErrorAggregator.php # Auto-triggered pattern analysis
|
|
│ ├── ErrorEvent.php # Single error event VO
|
|
│ ├── ErrorPattern.php # Pattern VO
|
|
│ ├── AlertManager.php # Alert queuing
|
|
│ ├── PatternDetector.php # Fingerprinting logic
|
|
│ └── Storage/
|
|
│ ├── ErrorStorageInterface.php
|
|
│ └── DatabaseErrorStorage.php # Unified database storage
|
|
│
|
|
├── Reporting/ # Error reporting & analytics
|
|
│ ├── ErrorReporter.php # Auto-triggered reporting
|
|
│ ├── ErrorReport.php # Report VO (extends ErrorEvent)
|
|
│ ├── AnalyticsEngine.php # Velocity & anomaly detection
|
|
│ ├── ReportFilters/ # Filter chain
|
|
│ └── ReportProcessors/ # Enrichment chain
|
|
│
|
|
├── Boundaries/ # Resilience & circuit breaker
|
|
│ ├── ErrorBoundary.php # Graceful degradation
|
|
│ ├── BoundaryConfig.php # Configuration VO
|
|
│ ├── BoundaryResult.php # Result pattern
|
|
│ ├── RetryStrategy.php # Retry patterns
|
|
│ ├── CircuitBreaker/
|
|
│ │ ├── CircuitBreakerManager.php
|
|
│ │ └── CircuitState.php
|
|
│ └── Exceptions/
|
|
│ ├── BoundaryException.php # Base boundary exception (extends FrameworkException)
|
|
│ ├── BoundaryTimeoutException.php
|
|
│ └── BoundaryFailedException.php
|
|
│
|
|
├── Http/ # HTTP-specific exceptions
|
|
│ ├── HttpException.php # Base HTTP exception
|
|
│ ├── NotFoundException.php
|
|
│ ├── UnauthorizedException.php
|
|
│ └── ...
|
|
│
|
|
├── Security/ # Security exceptions
|
|
│ ├── SecurityException.php
|
|
│ ├── XssAttemptException.php
|
|
│ └── ...
|
|
│
|
|
├── Database/ # Database exceptions
|
|
│ ├── DatabaseException.php
|
|
│ └── ...
|
|
│
|
|
├── Validation/ # Validation exceptions
|
|
│ ├── ValidationException.php
|
|
│ └── ...
|
|
│
|
|
└── Migrations/ # Database migrations
|
|
├── CreateErrorEventsTable.php
|
|
└── CreateErrorPatternsTable.php
|
|
```
|
|
|
|
### Removed Directories
|
|
|
|
```
|
|
❌ src/Framework/ErrorHandling/ # Merged into Exception/Handler/
|
|
❌ src/Framework/ErrorAggregation/ # Merged into Exception/Aggregation/
|
|
❌ src/Framework/ErrorBoundaries/ # Merged into Exception/Boundaries/
|
|
❌ src/Framework/ErrorReporting/ # Merged into Exception/Reporting/
|
|
```
|
|
|
|
## 2. Core Components Design
|
|
|
|
### 2.1 Enhanced FrameworkException
|
|
|
|
**File**: `src/Framework/Exception/Core/FrameworkException.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Core;
|
|
|
|
use App\Framework\Exception\Aggregation\ErrorAggregator;
|
|
use App\Framework\Exception\Reporting\ErrorReporter;
|
|
|
|
/**
|
|
* Enhanced FrameworkException with automatic integration
|
|
*
|
|
* All framework exceptions MUST extend this class to ensure:
|
|
* - Automatic pattern detection via ErrorAggregator
|
|
* - Automatic error reporting via ErrorReporter
|
|
* - Consistent error handling and context
|
|
* - Circuit breaker integration
|
|
*/
|
|
class FrameworkException extends \RuntimeException
|
|
{
|
|
protected ExceptionContext $context;
|
|
protected ?ErrorCode $errorCode = null;
|
|
protected ?int $retryAfter = null;
|
|
|
|
// NEW: Automatic integration flags
|
|
protected bool $skipAggregation = false;
|
|
protected bool $skipReporting = false;
|
|
protected bool $reported = false;
|
|
|
|
public function __construct(
|
|
string $message = '',
|
|
int $code = 0,
|
|
?\Throwable $previous = null
|
|
) {
|
|
parent::__construct($message, $code, $previous);
|
|
$this->context = ExceptionContext::empty();
|
|
}
|
|
|
|
// ========================================
|
|
// Factory Methods (existing + enhanced)
|
|
// ========================================
|
|
|
|
public static function simple(
|
|
string $message,
|
|
?\Throwable $previous = null,
|
|
int $httpStatus = 500
|
|
): static {
|
|
$exception = new static($message, $httpStatus, $previous);
|
|
return $exception;
|
|
}
|
|
|
|
public static function create(
|
|
ErrorCode $errorCode,
|
|
?string $message = null
|
|
): static {
|
|
$exception = new static($message ?? $errorCode->getDescription());
|
|
$exception->errorCode = $errorCode;
|
|
return $exception;
|
|
}
|
|
|
|
public static function forOperation(
|
|
string $operation,
|
|
?string $component = null,
|
|
?string $message = null,
|
|
?ErrorCode $errorCode = null
|
|
): static {
|
|
$exception = new static($message ?? "Error in {$operation}");
|
|
$exception->context = ExceptionContext::forOperation($operation, $component);
|
|
$exception->errorCode = $errorCode;
|
|
return $exception;
|
|
}
|
|
|
|
public static function fromContext(
|
|
string $message,
|
|
ExceptionContext $context,
|
|
?ErrorCode $errorCode = null
|
|
): static {
|
|
$exception = new static($message);
|
|
$exception->context = $context;
|
|
$exception->errorCode = $errorCode;
|
|
return $exception;
|
|
}
|
|
|
|
// ========================================
|
|
// Fluent API (existing)
|
|
// ========================================
|
|
|
|
public function withContext(ExceptionContext $context): self
|
|
{
|
|
$this->context = $context;
|
|
return $this;
|
|
}
|
|
|
|
public function withOperation(string $operation, ?string $component = null): self
|
|
{
|
|
$this->context = $this->context->withOperation($operation, $component);
|
|
return $this;
|
|
}
|
|
|
|
public function withData(array $data): self
|
|
{
|
|
$this->context = $this->context->withData($data);
|
|
return $this;
|
|
}
|
|
|
|
public function withDebug(array $debug): self
|
|
{
|
|
$this->context = $this->context->withDebug($debug);
|
|
return $this;
|
|
}
|
|
|
|
public function withMetadata(array $metadata): self
|
|
{
|
|
$this->context = $this->context->withMetadata($metadata);
|
|
return $this;
|
|
}
|
|
|
|
public function withErrorCode(ErrorCode $errorCode): self
|
|
{
|
|
$this->errorCode = $errorCode;
|
|
return $this;
|
|
}
|
|
|
|
public function withRetryAfter(int $seconds): self
|
|
{
|
|
$this->retryAfter = $seconds;
|
|
return $this;
|
|
}
|
|
|
|
// NEW: Request/System context methods
|
|
public function withRequestContext(
|
|
string $requestId,
|
|
string $method,
|
|
string $uri,
|
|
?string $clientIp = null,
|
|
?string $userAgent = null
|
|
): self {
|
|
$this->context = $this->context->withRequestContext(
|
|
$requestId,
|
|
$method,
|
|
$uri,
|
|
$clientIp,
|
|
$userAgent
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
public function withSystemContext(
|
|
string $environment,
|
|
string $hostname,
|
|
float $memoryUsage,
|
|
float $cpuUsage
|
|
): self {
|
|
$this->context = $this->context->withSystemContext(
|
|
$environment,
|
|
$hostname,
|
|
$memoryUsage,
|
|
$cpuUsage
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
// NEW: Integration control methods
|
|
public function skipAggregation(): self
|
|
{
|
|
$this->skipAggregation = true;
|
|
return $this;
|
|
}
|
|
|
|
public function skipReporting(): self
|
|
{
|
|
$this->skipReporting = true;
|
|
return $this;
|
|
}
|
|
|
|
// ========================================
|
|
// Getters
|
|
// ========================================
|
|
|
|
public function getContext(): ExceptionContext
|
|
{
|
|
return $this->context;
|
|
}
|
|
|
|
public function getErrorCode(): ?ErrorCode
|
|
{
|
|
return $this->errorCode;
|
|
}
|
|
|
|
public function getRetryAfter(): ?int
|
|
{
|
|
return $this->retryAfter;
|
|
}
|
|
|
|
// NEW: Get severity from ErrorCode
|
|
public function getSeverity(): ErrorSeverity
|
|
{
|
|
return $this->errorCode?->getSeverity() ?? ErrorSeverity::ERROR;
|
|
}
|
|
|
|
// ========================================
|
|
// Recovery Methods
|
|
// ========================================
|
|
|
|
public function isRecoverable(): bool
|
|
{
|
|
return $this->errorCode?->isRecoverable() ?? true;
|
|
}
|
|
|
|
public function getRecoveryHint(): ?string
|
|
{
|
|
return $this->errorCode?->getRecoveryHint();
|
|
}
|
|
|
|
// NEW: Check if already reported
|
|
public function isReported(): bool
|
|
{
|
|
return $this->reported;
|
|
}
|
|
|
|
public function markAsReported(): void
|
|
{
|
|
$this->reported = true;
|
|
}
|
|
|
|
public function shouldSkipAggregation(): bool
|
|
{
|
|
return $this->skipAggregation;
|
|
}
|
|
|
|
public function shouldSkipReporting(): bool
|
|
{
|
|
return $this->skipReporting;
|
|
}
|
|
|
|
// ========================================
|
|
// Category Helpers
|
|
// ========================================
|
|
|
|
public function isCategory(string $category): bool
|
|
{
|
|
if ($this->errorCode === null) {
|
|
return false;
|
|
}
|
|
|
|
return $this->errorCode->getCategory() === $category;
|
|
}
|
|
|
|
public function isErrorCode(ErrorCode $errorCode): bool
|
|
{
|
|
return $this->errorCode === $errorCode;
|
|
}
|
|
|
|
// ========================================
|
|
// Serialization
|
|
// ========================================
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'message' => $this->getMessage(),
|
|
'code' => $this->getCode(),
|
|
'error_code' => $this->errorCode?->value,
|
|
'severity' => $this->getSeverity()->value,
|
|
'context' => $this->context->toArray(),
|
|
'file' => $this->getFile(),
|
|
'line' => $this->getLine(),
|
|
'recoverable' => $this->isRecoverable(),
|
|
'retry_after' => $this->retryAfter,
|
|
'recovery_hint' => $this->getRecoveryHint(),
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key Enhancements**:
|
|
1. ✅ Automatic integration flags (`skipAggregation`, `skipReporting`, `reported`)
|
|
2. ✅ Request/System context support via `withRequestContext()`, `withSystemContext()`
|
|
3. ✅ `getSeverity()` from ErrorCode
|
|
4. ✅ Integration control methods (`skipAggregation()`, `skipReporting()`)
|
|
5. ✅ Category helpers (`isCategory()`, `isErrorCode()`)
|
|
|
|
### 2.2 Unified ExceptionContext
|
|
|
|
**File**: `src/Framework/Exception/Core/ExceptionContext.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Core;
|
|
|
|
/**
|
|
* Unified ExceptionContext combining Domain + Request + System context
|
|
*
|
|
* Replaces both:
|
|
* - ExceptionContext (old)
|
|
* - ErrorHandlerContext (old)
|
|
*/
|
|
final readonly class ExceptionContext
|
|
{
|
|
public function __construct(
|
|
// Domain Context
|
|
public ?string $operation = null,
|
|
public ?string $component = null,
|
|
public array $data = [],
|
|
public array $debug = [],
|
|
public array $metadata = [],
|
|
|
|
// Request Context (NEW)
|
|
public ?string $requestId = null,
|
|
public ?string $requestMethod = null,
|
|
public ?string $requestUri = null,
|
|
public ?string $clientIp = null,
|
|
public ?string $userAgent = null,
|
|
public ?string $userId = null,
|
|
|
|
// System Context (NEW)
|
|
public ?string $environment = null,
|
|
public ?string $hostname = null,
|
|
public ?float $memoryUsage = null,
|
|
public ?float $cpuUsage = null,
|
|
public ?string $phpVersion = null,
|
|
) {
|
|
}
|
|
|
|
// ========================================
|
|
// Factory Methods
|
|
// ========================================
|
|
|
|
public static function empty(): self
|
|
{
|
|
return new self();
|
|
}
|
|
|
|
public static function forOperation(string $operation, ?string $component = null): self
|
|
{
|
|
return new self(operation: $operation, component: $component);
|
|
}
|
|
|
|
public static function fromRequest(
|
|
string $requestId,
|
|
string $method,
|
|
string $uri,
|
|
?string $clientIp = null,
|
|
?string $userAgent = null,
|
|
?string $userId = null
|
|
): self {
|
|
return new self(
|
|
requestId: $requestId,
|
|
requestMethod: $method,
|
|
requestUri: $uri,
|
|
clientIp: $clientIp,
|
|
userAgent: $userAgent,
|
|
userId: $userId
|
|
);
|
|
}
|
|
|
|
public static function fromSystem(): self
|
|
{
|
|
return new self(
|
|
environment: $_ENV['APP_ENV'] ?? 'production',
|
|
hostname: gethostname() ?: 'unknown',
|
|
memoryUsage: memory_get_usage(true) / 1024 / 1024, // MB
|
|
cpuUsage: sys_getloadavg()[0] ?? 0.0,
|
|
phpVersion: PHP_VERSION
|
|
);
|
|
}
|
|
|
|
// ========================================
|
|
// Fluent API - Domain Context
|
|
// ========================================
|
|
|
|
public function withOperation(string $operation, ?string $component = null): self
|
|
{
|
|
return new self(
|
|
operation: $operation,
|
|
component: $component ?? $this->component,
|
|
data: $this->data,
|
|
debug: $this->debug,
|
|
metadata: $this->metadata,
|
|
requestId: $this->requestId,
|
|
requestMethod: $this->requestMethod,
|
|
requestUri: $this->requestUri,
|
|
clientIp: $this->clientIp,
|
|
userAgent: $this->userAgent,
|
|
userId: $this->userId,
|
|
environment: $this->environment,
|
|
hostname: $this->hostname,
|
|
memoryUsage: $this->memoryUsage,
|
|
cpuUsage: $this->cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
public function withData(array $data): self
|
|
{
|
|
return new self(
|
|
operation: $this->operation,
|
|
component: $this->component,
|
|
data: array_merge($this->data, $data),
|
|
debug: $this->debug,
|
|
metadata: $this->metadata,
|
|
requestId: $this->requestId,
|
|
requestMethod: $this->requestMethod,
|
|
requestUri: $this->requestUri,
|
|
clientIp: $this->clientIp,
|
|
userAgent: $this->userAgent,
|
|
userId: $this->userId,
|
|
environment: $this->environment,
|
|
hostname: $this->hostname,
|
|
memoryUsage: $this->memoryUsage,
|
|
cpuUsage: $this->cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
public function withDebug(array $debug): self
|
|
{
|
|
return new self(
|
|
operation: $this->operation,
|
|
component: $this->component,
|
|
data: $this->data,
|
|
debug: array_merge($this->debug, $debug),
|
|
metadata: $this->metadata,
|
|
requestId: $this->requestId,
|
|
requestMethod: $this->requestMethod,
|
|
requestUri: $this->requestUri,
|
|
clientIp: $this->clientIp,
|
|
userAgent: $this->userAgent,
|
|
userId: $this->userId,
|
|
environment: $this->environment,
|
|
hostname: $this->hostname,
|
|
memoryUsage: $this->memoryUsage,
|
|
cpuUsage: $this->cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
public function withMetadata(array $metadata): self
|
|
{
|
|
return new self(
|
|
operation: $this->operation,
|
|
component: $this->component,
|
|
data: $this->data,
|
|
debug: $this->debug,
|
|
metadata: array_merge($this->metadata, $metadata),
|
|
requestId: $this->requestId,
|
|
requestMethod: $this->requestMethod,
|
|
requestUri: $this->requestUri,
|
|
clientIp: $this->clientIp,
|
|
userAgent: $this->userAgent,
|
|
userId: $this->userId,
|
|
environment: $this->environment,
|
|
hostname: $this->hostname,
|
|
memoryUsage: $this->memoryUsage,
|
|
cpuUsage: $this->cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
// ========================================
|
|
// Fluent API - Request Context
|
|
// ========================================
|
|
|
|
public function withRequestContext(
|
|
string $requestId,
|
|
string $method,
|
|
string $uri,
|
|
?string $clientIp = null,
|
|
?string $userAgent = null,
|
|
?string $userId = null
|
|
): self {
|
|
return new self(
|
|
operation: $this->operation,
|
|
component: $this->component,
|
|
data: $this->data,
|
|
debug: $this->debug,
|
|
metadata: $this->metadata,
|
|
requestId: $requestId,
|
|
requestMethod: $method,
|
|
requestUri: $uri,
|
|
clientIp: $clientIp,
|
|
userAgent: $userAgent,
|
|
userId: $userId,
|
|
environment: $this->environment,
|
|
hostname: $this->hostname,
|
|
memoryUsage: $this->memoryUsage,
|
|
cpuUsage: $this->cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
public function withUserId(string $userId): self
|
|
{
|
|
return new self(
|
|
operation: $this->operation,
|
|
component: $this->component,
|
|
data: $this->data,
|
|
debug: $this->debug,
|
|
metadata: $this->metadata,
|
|
requestId: $this->requestId,
|
|
requestMethod: $this->requestMethod,
|
|
requestUri: $this->requestUri,
|
|
clientIp: $this->clientIp,
|
|
userAgent: $this->userAgent,
|
|
userId: $userId,
|
|
environment: $this->environment,
|
|
hostname: $this->hostname,
|
|
memoryUsage: $this->memoryUsage,
|
|
cpuUsage: $this->cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
// ========================================
|
|
// Fluent API - System Context
|
|
// ========================================
|
|
|
|
public function withSystemContext(
|
|
string $environment,
|
|
string $hostname,
|
|
float $memoryUsage,
|
|
float $cpuUsage
|
|
): self {
|
|
return new self(
|
|
operation: $this->operation,
|
|
component: $this->component,
|
|
data: $this->data,
|
|
debug: $this->debug,
|
|
metadata: $this->metadata,
|
|
requestId: $this->requestId,
|
|
requestMethod: $this->requestMethod,
|
|
requestUri: $this->requestUri,
|
|
clientIp: $this->clientIp,
|
|
userAgent: $this->userAgent,
|
|
userId: $this->userId,
|
|
environment: $environment,
|
|
hostname: $hostname,
|
|
memoryUsage: $memoryUsage,
|
|
cpuUsage: $cpuUsage,
|
|
phpVersion: $this->phpVersion
|
|
);
|
|
}
|
|
|
|
// ========================================
|
|
// Getters (for backward compatibility)
|
|
// ========================================
|
|
|
|
public function getOperation(): ?string
|
|
{
|
|
return $this->operation;
|
|
}
|
|
|
|
public function getComponent(): ?string
|
|
{
|
|
return $this->component;
|
|
}
|
|
|
|
public function getData(): array
|
|
{
|
|
return $this->data;
|
|
}
|
|
|
|
public function getDebug(): array
|
|
{
|
|
return $this->debug;
|
|
}
|
|
|
|
public function getMetadata(): array
|
|
{
|
|
return $this->metadata;
|
|
}
|
|
|
|
// ========================================
|
|
// Serialization
|
|
// ========================================
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
// Domain context
|
|
'operation' => $this->operation,
|
|
'component' => $this->component,
|
|
'data' => $this->sanitizeData($this->data),
|
|
'debug' => $this->debug,
|
|
'metadata' => $this->metadata,
|
|
|
|
// Request context
|
|
'request' => [
|
|
'id' => $this->requestId,
|
|
'method' => $this->requestMethod,
|
|
'uri' => $this->requestUri,
|
|
'client_ip' => $this->clientIp,
|
|
'user_agent' => $this->userAgent,
|
|
'user_id' => $this->userId,
|
|
],
|
|
|
|
// System context
|
|
'system' => [
|
|
'environment' => $this->environment,
|
|
'hostname' => $this->hostname,
|
|
'memory_usage_mb' => $this->memoryUsage,
|
|
'cpu_usage' => $this->cpuUsage,
|
|
'php_version' => $this->phpVersion,
|
|
],
|
|
];
|
|
}
|
|
|
|
private function sanitizeData(array $data): array
|
|
{
|
|
$sensitiveKeys = ['password', 'token', 'api_key', 'secret', 'private_key'];
|
|
|
|
array_walk_recursive($data, function (&$value, $key) use ($sensitiveKeys) {
|
|
if (in_array(strtolower($key), $sensitiveKeys)) {
|
|
$value = '[REDACTED]';
|
|
}
|
|
});
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key Enhancements**:
|
|
1. ✅ Unified context (Domain + Request + System)
|
|
2. ✅ Replaces both ExceptionContext and ErrorHandlerContext
|
|
3. ✅ Factory methods for different context types
|
|
4. ✅ Fluent API preserved and extended
|
|
5. ✅ Automatic data sanitization
|
|
|
|
### 2.3 Enhanced ErrorCode with Severity Mapping
|
|
|
|
**File**: `src/Framework/Exception/Core/ErrorCode.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Core;
|
|
|
|
enum ErrorCode: string
|
|
{
|
|
// ... (alle bestehenden 100+ ErrorCodes bleiben)
|
|
|
|
// NEW: Severity mapping
|
|
public function getSeverity(): ErrorSeverity
|
|
{
|
|
return match($this) {
|
|
// CRITICAL Severity
|
|
self::SYSTEM_RESOURCE_EXHAUSTED,
|
|
self::DB_CONNECTION_FAILED,
|
|
self::DB_POOL_EXHAUSTED,
|
|
self::SEC_SQL_INJECTION_ATTEMPT,
|
|
self::SEC_XSS_ATTEMPT,
|
|
self::SEC_PATH_TRAVERSAL_ATTEMPT,
|
|
self::PERF_MEMORY_LIMIT_EXCEEDED,
|
|
self::PERF_CIRCUIT_BREAKER_OPEN,
|
|
self::FS_DISK_FULL => ErrorSeverity::CRITICAL,
|
|
|
|
// ERROR Severity
|
|
self::SYSTEM_DEPENDENCY_MISSING,
|
|
self::DB_QUERY_FAILED,
|
|
self::DB_TRANSACTION_FAILED,
|
|
self::DB_MIGRATION_FAILED,
|
|
self::AUTH_CREDENTIALS_INVALID,
|
|
self::AUTH_INSUFFICIENT_PRIVILEGES,
|
|
self::CACHE_CONNECTION_FAILED,
|
|
self::FS_FILE_NOT_FOUND,
|
|
self::FS_PERMISSION_DENIED,
|
|
self::API_SERVICE_UNAVAILABLE,
|
|
self::QUEUE_CONNECTION_FAILED,
|
|
self::QUEUE_JOB_FAILED,
|
|
self::PERF_EXECUTION_TIMEOUT,
|
|
self::EVENT_DISPATCH_FAILED,
|
|
self::TPL_RENDERING_FAILED => ErrorSeverity::ERROR,
|
|
|
|
// WARNING Severity
|
|
self::SYSTEM_CONFIG_INVALID,
|
|
self::DB_CONSTRAINT_VIOLATION,
|
|
self::AUTH_TOKEN_EXPIRED,
|
|
self::AUTH_SESSION_EXPIRED,
|
|
self::HTTP_RATE_LIMIT_EXCEEDED,
|
|
self::API_RATE_LIMIT_EXCEEDED,
|
|
self::CACHE_KEY_NOT_FOUND,
|
|
self::QUEUE_MAX_RETRIES_EXCEEDED,
|
|
self::PERF_THRESHOLD_EXCEEDED,
|
|
self::TPL_TEMPLATE_NOT_FOUND => ErrorSeverity::WARNING,
|
|
|
|
// INFO Severity
|
|
self::HTTP_NOT_FOUND,
|
|
self::ENTITY_NOT_FOUND,
|
|
self::VAL_REQUIRED_FIELD_MISSING,
|
|
self::VAL_INVALID_FORMAT,
|
|
self::VAL_OUT_OF_RANGE => ErrorSeverity::INFO,
|
|
|
|
// DEBUG Severity
|
|
self::DISC_CACHE_CORRUPTION,
|
|
self::TPL_CACHE_FAILED => ErrorSeverity::DEBUG,
|
|
|
|
// Default to ERROR for unmapped codes
|
|
default => ErrorSeverity::ERROR,
|
|
};
|
|
}
|
|
|
|
// Existing methods remain unchanged
|
|
public function getCategory(): string { ... }
|
|
public function getNumericCode(): int { ... }
|
|
public function getDescription(): string { ... }
|
|
public function getRecoveryHint(): string { ... }
|
|
public function isRecoverable(): bool { ... }
|
|
public function getRetryAfterSeconds(): ?int { ... }
|
|
}
|
|
```
|
|
|
|
**Key Enhancements**:
|
|
1. ✅ `getSeverity()` method mapping all 100+ codes to severities
|
|
2. ✅ Explicit severity for each category
|
|
3. ✅ Fallback to ERROR for unmapped codes
|
|
|
|
### 2.4 Integrated ErrorHandler
|
|
|
|
**File**: `src/Framework/Exception/Handler/ErrorHandler.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Handler;
|
|
|
|
use App\Framework\Exception\Aggregation\ErrorAggregator;
|
|
use App\Framework\Exception\Core\FrameworkException;
|
|
use App\Framework\Exception\Reporting\ErrorReporter;
|
|
use App\Framework\Http\Response;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Integrated ErrorHandler with automatic aggregation and reporting
|
|
*/
|
|
final readonly class ErrorHandler
|
|
{
|
|
public function __construct(
|
|
private ErrorLogger $logger,
|
|
private ResponseFactory $responseFactory,
|
|
private ContextBuilder $contextBuilder,
|
|
private ErrorAggregator $aggregator, // NEW: Auto-integrated
|
|
private ErrorReporter $reporter, // NEW: Auto-integrated
|
|
private bool $enableAggregation = true,
|
|
private bool $enableReporting = true,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Register global error handlers
|
|
*/
|
|
public function register(): void
|
|
{
|
|
set_exception_handler($this->handleException(...));
|
|
set_error_handler($this->handleError(...));
|
|
register_shutdown_function($this->handleShutdown(...));
|
|
}
|
|
|
|
/**
|
|
* Handle exception with automatic integration
|
|
*/
|
|
public function handleException(Throwable $exception): void
|
|
{
|
|
try {
|
|
// 1. Build unified context
|
|
$context = $this->contextBuilder->buildFromException($exception);
|
|
|
|
// 2. Enrich FrameworkException with full context
|
|
if ($exception instanceof FrameworkException) {
|
|
$exception = $this->enrichException($exception, $context);
|
|
}
|
|
|
|
// 3. Log the exception
|
|
$this->logger->logException($exception, $context);
|
|
|
|
// 4. Auto-trigger aggregation (if enabled and not skipped)
|
|
if ($this->enableAggregation && !$this->shouldSkipAggregation($exception)) {
|
|
$this->aggregator->processException($exception, $context);
|
|
}
|
|
|
|
// 5. Auto-trigger reporting (if enabled and not skipped)
|
|
if ($this->enableReporting && !$this->shouldSkipReporting($exception)) {
|
|
$this->reporter->reportException($exception, $context);
|
|
}
|
|
|
|
// 6. Create HTTP response (only if in HTTP context)
|
|
if ($this->isHttpContext()) {
|
|
$response = $this->responseFactory->createFromException($exception, $context);
|
|
$this->emitResponse($response);
|
|
}
|
|
|
|
} catch (Throwable $e) {
|
|
// Fallback: Don't let error handling crash the application
|
|
$this->handleErrorHandlerFailure($e, $exception);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enrich FrameworkException with full context
|
|
*/
|
|
private function enrichException(
|
|
FrameworkException $exception,
|
|
ExceptionContext $fullContext
|
|
): FrameworkException {
|
|
// Only enrich if exception doesn't already have full context
|
|
if ($fullContext->requestId && !$exception->getContext()->requestId) {
|
|
return $exception->withContext($fullContext);
|
|
}
|
|
|
|
return $exception;
|
|
}
|
|
|
|
/**
|
|
* Check if aggregation should be skipped
|
|
*/
|
|
private function shouldSkipAggregation(Throwable $exception): bool
|
|
{
|
|
if ($exception instanceof FrameworkException) {
|
|
return $exception->shouldSkipAggregation();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if reporting should be skipped
|
|
*/
|
|
private function shouldSkipReporting(Throwable $exception): bool
|
|
{
|
|
if ($exception instanceof FrameworkException) {
|
|
return $exception->shouldSkipReporting() || $exception->isReported();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create HTTP response from exception
|
|
*/
|
|
public function createHttpResponse(
|
|
Throwable $exception,
|
|
?ExceptionContext $context = null
|
|
): Response {
|
|
$context ??= $this->contextBuilder->buildFromException($exception);
|
|
return $this->responseFactory->createFromException($exception, $context);
|
|
}
|
|
|
|
// ... (rest of existing ErrorHandler methods)
|
|
}
|
|
```
|
|
|
|
**Key Enhancements**:
|
|
1. ✅ Automatic ErrorAggregator integration
|
|
2. ✅ Automatic ErrorReporter integration
|
|
3. ✅ Respects `skipAggregation()` and `skipReporting()` flags
|
|
4. ✅ Prevents duplicate reporting via `isReported()` check
|
|
5. ✅ Graceful fallback if integration fails
|
|
|
|
### 2.5 Auto-Triggered ErrorAggregator
|
|
|
|
**File**: `src/Framework/Exception/Aggregation/ErrorAggregator.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Aggregation;
|
|
|
|
use App\Framework\Exception\Core\ExceptionContext;
|
|
use App\Framework\Exception\Core\FrameworkException;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Auto-triggered ErrorAggregator (called by ErrorHandler)
|
|
*/
|
|
final readonly class ErrorAggregator
|
|
{
|
|
// ... (existing constructor and properties)
|
|
|
|
/**
|
|
* NEW: Process exception (called by ErrorHandler)
|
|
*/
|
|
public function processException(
|
|
Throwable $exception,
|
|
ExceptionContext $context
|
|
): void {
|
|
try {
|
|
// Create ErrorEvent from exception + context
|
|
$errorEvent = ErrorEvent::fromException($exception, $context);
|
|
|
|
// Use existing processErrorEvent logic
|
|
$this->processErrorEvent($errorEvent);
|
|
|
|
} catch (\Throwable $e) {
|
|
// Don't let aggregation break the application
|
|
$this->logError("Failed to process exception: " . $e->getMessage(), [
|
|
'exception' => $e,
|
|
'original_exception' => $exception->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Existing: Process ErrorEvent
|
|
*/
|
|
public function processErrorEvent(ErrorEvent $event): void
|
|
{
|
|
// Existing logic remains unchanged
|
|
$this->storage->storeEvent($event);
|
|
$pattern = $this->updateErrorPattern($event);
|
|
|
|
if ($pattern->shouldAlert()) {
|
|
$this->queueAlert($pattern, $event);
|
|
}
|
|
|
|
$this->logError("Error processed", [
|
|
'event_id' => $event->id->toString(),
|
|
'fingerprint' => $event->getFingerprint(),
|
|
'pattern_id' => $pattern->id->toString(),
|
|
]);
|
|
}
|
|
|
|
// ... (rest of existing methods remain unchanged)
|
|
}
|
|
```
|
|
|
|
**Key Enhancements**:
|
|
1. ✅ New `processException()` method for ErrorHandler integration
|
|
2. ✅ Creates ErrorEvent from Throwable + ExceptionContext
|
|
3. ✅ Delegates to existing `processErrorEvent()` logic
|
|
4. ✅ Graceful error handling
|
|
|
|
### 2.6 Enhanced ErrorEvent
|
|
|
|
**File**: `src/Framework/Exception/Aggregation/ErrorEvent.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Aggregation;
|
|
|
|
use App\Framework\Exception\Core\ExceptionContext;
|
|
use App\Framework\Exception\Core\FrameworkException;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Enhanced ErrorEvent with Throwable support
|
|
*/
|
|
final readonly class ErrorEvent
|
|
{
|
|
// ... (existing properties remain)
|
|
|
|
/**
|
|
* NEW: Create from Throwable + ExceptionContext
|
|
*/
|
|
public static function fromException(
|
|
Throwable $exception,
|
|
ExceptionContext $context
|
|
): self {
|
|
// Extract ErrorCode (if FrameworkException)
|
|
$errorCode = $exception instanceof FrameworkException
|
|
? $exception->getErrorCode() ?? self::inferErrorCode($exception)
|
|
: self::inferErrorCode($exception);
|
|
|
|
// Extract severity
|
|
$severity = $exception instanceof FrameworkException
|
|
? $exception->getSeverity()
|
|
: self::inferSeverity($exception);
|
|
|
|
return new self(
|
|
id: Ulid::generate(),
|
|
service: self::extractServiceName($context),
|
|
component: $context->component ?? 'unknown',
|
|
operation: $context->operation ?? 'unknown',
|
|
errorCode: $errorCode,
|
|
errorMessage: $exception->getMessage(),
|
|
severity: $severity,
|
|
occurredAt: new \DateTimeImmutable(),
|
|
context: $context->data,
|
|
metadata: $context->metadata,
|
|
requestId: $context->requestId,
|
|
userId: $context->userId,
|
|
clientIp: $context->clientIp,
|
|
isSecurityEvent: self::isSecurityException($exception),
|
|
stackTrace: $exception->getTraceAsString(),
|
|
userAgent: $context->userAgent,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Existing: Create from ErrorHandlerContext (backward compatibility)
|
|
*/
|
|
public static function fromErrorHandlerContext(ErrorHandlerContext $context): self
|
|
{
|
|
// Existing logic remains for backward compatibility during migration
|
|
// Will be deprecated after migration complete
|
|
}
|
|
|
|
/**
|
|
* Infer ErrorCode from generic Throwable
|
|
*/
|
|
private static function inferErrorCode(Throwable $exception): ErrorCode
|
|
{
|
|
return match (true) {
|
|
$exception instanceof \PDOException => ErrorCode::DB_QUERY_FAILED,
|
|
$exception instanceof \RuntimeException => ErrorCode::SYSTEM_RESOURCE_EXHAUSTED,
|
|
$exception instanceof \InvalidArgumentException => ErrorCode::VAL_INVALID_INPUT,
|
|
default => ErrorCode::SYSTEM_RESOURCE_EXHAUSTED,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Infer severity from generic Throwable
|
|
*/
|
|
private static function inferSeverity(Throwable $exception): ErrorSeverity
|
|
{
|
|
return match (true) {
|
|
$exception instanceof \Error => ErrorSeverity::CRITICAL,
|
|
$exception instanceof \PDOException => ErrorSeverity::ERROR,
|
|
$exception instanceof \RuntimeException => ErrorSeverity::ERROR,
|
|
default => ErrorSeverity::WARNING,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if exception is security-related
|
|
*/
|
|
private static function isSecurityException(Throwable $exception): bool
|
|
{
|
|
if ($exception instanceof FrameworkException) {
|
|
return $exception->isCategory('SEC');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ... (rest of existing methods remain unchanged)
|
|
}
|
|
```
|
|
|
|
**Key Enhancements**:
|
|
1. ✅ New `fromException()` factory method
|
|
2. ✅ Automatic ErrorCode inference for non-FrameworkException
|
|
3. ✅ Automatic severity inference
|
|
4. ✅ Security exception detection
|
|
5. ✅ Backward compatibility with `fromErrorHandlerContext()`
|
|
|
|
## 3. Integration Architecture
|
|
|
|
### 3.1 Automatic Integration Flow
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 1. Exception Thrown │
|
|
│ throw FrameworkException::create(ErrorCode::DB_QUERY_FAILED) │
|
|
└──────────────────────┬──────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 2. ErrorHandler::handleException() │
|
|
│ - Build ExceptionContext (Domain + Request + System) │
|
|
│ - Enrich FrameworkException with full context │
|
|
│ - Log exception via ErrorLogger │
|
|
└──────────────────────┬──────────────────────────────────────────┘
|
|
│
|
|
┌──────────────┼──────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ 3a. Aggregate│ │ 3b. Report │ │ 3c. Respond │
|
|
│ (async) │ │ (async) │ │ (sync) │
|
|
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ ErrorEvent │ │ ErrorReport │ │ HTTP Response│
|
|
│ ErrorPattern │ │ Analytics │ │ JSON/HTML │
|
|
│ Alert Queue │ │ Storage │ │ │
|
|
└──────────────┘ └──────────────┘ └──────────────┘
|
|
```
|
|
|
|
### 3.2 ErrorBoundaries Integration
|
|
|
|
**File**: `src/Framework/Exception/Boundaries/Exceptions/BoundaryException.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Boundaries\Exceptions;
|
|
|
|
use App\Framework\Exception\Core\ErrorCode;
|
|
use App\Framework\Exception\Core\FrameworkException;
|
|
|
|
/**
|
|
* Base exception for ErrorBoundaries (extends FrameworkException)
|
|
*/
|
|
class BoundaryException extends FrameworkException
|
|
{
|
|
public function __construct(
|
|
string $message,
|
|
string $boundaryName,
|
|
?\Throwable $previous = null
|
|
) {
|
|
parent::__construct($message, 0, $previous);
|
|
|
|
$this->withOperation('boundary.execute', 'ErrorBoundary')
|
|
->withData(['boundary_name' => $boundaryName])
|
|
->withErrorCode(ErrorCode::PERF_CIRCUIT_BREAKER_OPEN);
|
|
}
|
|
}
|
|
```
|
|
|
|
**File**: `src/Framework/Exception/Boundaries/Exceptions/BoundaryTimeoutException.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Boundaries\Exceptions;
|
|
|
|
use App\Framework\Exception\Core\ErrorCode;
|
|
|
|
final class BoundaryTimeoutException extends BoundaryException
|
|
{
|
|
public function __construct(
|
|
string $message,
|
|
string $boundaryName,
|
|
float $executionTimeSeconds,
|
|
?\Throwable $previous = null
|
|
) {
|
|
parent::__construct($message, $boundaryName, $previous);
|
|
|
|
$this->withData([
|
|
'execution_time_seconds' => $executionTimeSeconds,
|
|
'timeout_type' => 'boundary_timeout',
|
|
])->withErrorCode(ErrorCode::PERF_EXECUTION_TIMEOUT);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key Changes**:
|
|
1. ✅ BoundaryException extends FrameworkException
|
|
2. ✅ Inherits ErrorCode, ExceptionContext, retry logic
|
|
3. ✅ Automatically integrated with ErrorHandler
|
|
4. ✅ Benefits from pattern detection and reporting
|
|
|
|
### 3.3 Database Schema Consolidation
|
|
|
|
**Migration**: `src/Framework/Exception/Migrations/CreateErrorEventsTable.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
use App\Framework\Database\Schema\Blueprint;
|
|
use App\Framework\Database\Schema\Schema;
|
|
|
|
return new class {
|
|
public function up(Schema $schema): void
|
|
{
|
|
$schema->create('error_events', function (Blueprint $table) {
|
|
// Primary key
|
|
$table->string('id', 26)->primary(); // ULID
|
|
|
|
// Domain context
|
|
$table->string('service', 50);
|
|
$table->string('component', 100);
|
|
$table->string('operation', 100);
|
|
$table->string('error_code', 20);
|
|
$table->text('error_message');
|
|
$table->string('severity', 20); // CRITICAL, ERROR, WARNING, INFO, DEBUG
|
|
|
|
// Timing
|
|
$table->timestamp('occurred_at');
|
|
|
|
// Context data (JSON)
|
|
$table->json('context')->nullable();
|
|
$table->json('metadata')->nullable();
|
|
|
|
// Request context
|
|
$table->string('request_id', 50)->nullable();
|
|
$table->string('user_id', 50)->nullable();
|
|
$table->string('client_ip', 45)->nullable(); // IPv6 support
|
|
$table->text('user_agent')->nullable();
|
|
|
|
// Security
|
|
$table->boolean('is_security_event')->default(false);
|
|
|
|
// Debugging
|
|
$table->text('stack_trace')->nullable();
|
|
|
|
// NEW: Reporting fields (merged from error_reports)
|
|
$table->string('report_level', 20)->nullable(); // error, warning, info, debug
|
|
$table->json('environment')->nullable(); // System context
|
|
$table->boolean('is_reported')->default(false);
|
|
$table->timestamp('reported_at')->nullable();
|
|
|
|
// Indexes for performance
|
|
$table->index(['service', 'occurred_at']);
|
|
$table->index(['error_code', 'occurred_at']);
|
|
$table->index(['severity', 'occurred_at']);
|
|
$table->index('request_id');
|
|
$table->index('user_id');
|
|
$table->index(['is_security_event', 'occurred_at']);
|
|
$table->index('occurred_at'); // For cleanup queries
|
|
});
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
$schema->drop('error_events');
|
|
}
|
|
};
|
|
```
|
|
|
|
**Migration**: `src/Framework/Exception/Migrations/CreateErrorPatternsTable.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
use App\Framework\Database\Schema\Blueprint;
|
|
use App\Framework\Database\Schema\Schema;
|
|
|
|
return new class {
|
|
public function up(Schema $schema): void
|
|
{
|
|
$schema->create('error_patterns', function (Blueprint $table) {
|
|
// Primary key
|
|
$table->string('id', 26)->primary(); // ULID
|
|
|
|
// Pattern identification
|
|
$table->string('fingerprint', 64)->unique(); // SHA-256 hash
|
|
|
|
// Pattern metadata
|
|
$table->string('service', 50);
|
|
$table->string('component', 100);
|
|
$table->string('operation', 100);
|
|
$table->string('error_code', 20);
|
|
$table->text('normalized_message');
|
|
$table->string('severity', 20);
|
|
|
|
// Occurrence tracking
|
|
$table->integer('occurrence_count')->default(1);
|
|
$table->timestamp('first_occurrence');
|
|
$table->timestamp('last_occurrence');
|
|
|
|
// Impact tracking (JSON arrays)
|
|
$table->json('affected_users')->nullable(); // Array of user IDs
|
|
$table->json('affected_ips')->nullable(); // Array of IP addresses
|
|
|
|
// Pattern lifecycle
|
|
$table->boolean('is_active')->default(true);
|
|
$table->boolean('is_acknowledged')->default(false);
|
|
$table->string('acknowledged_by', 100)->nullable();
|
|
$table->timestamp('acknowledged_at')->nullable();
|
|
$table->text('resolution')->nullable();
|
|
|
|
// Additional metadata
|
|
$table->json('metadata')->nullable();
|
|
|
|
// Timestamps
|
|
$table->timestamp('created_at');
|
|
$table->timestamp('updated_at');
|
|
|
|
// Indexes for performance
|
|
$table->index(['service', 'is_active', 'last_occurrence']);
|
|
$table->index(['error_code', 'is_active']);
|
|
$table->index(['severity', 'is_active', 'occurrence_count']);
|
|
$table->index(['is_active', 'is_acknowledged']);
|
|
$table->index('last_occurrence'); // For cleanup queries
|
|
});
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
$schema->drop('error_patterns');
|
|
}
|
|
};
|
|
```
|
|
|
|
**Key Changes**:
|
|
1. ✅ Merged `error_reports` fields into `error_events`
|
|
2. ✅ Single source of truth für error storage
|
|
3. ✅ Optimized indexes for common queries
|
|
4. ✅ Retained all functionality from both tables
|
|
|
|
### 3.4 Backward Compatibility Strategy
|
|
|
|
**Compatibility Layer**: `src/Framework/Exception/Compatibility/ErrorHandlerContextAdapter.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Compatibility;
|
|
|
|
use App\Framework\Exception\Core\ExceptionContext;
|
|
|
|
/**
|
|
* Adapter for old ErrorHandlerContext (backward compatibility)
|
|
*
|
|
* @deprecated Will be removed in next major version
|
|
*/
|
|
final readonly class ErrorHandlerContextAdapter
|
|
{
|
|
/**
|
|
* Convert old ErrorHandlerContext to new ExceptionContext
|
|
*/
|
|
public static function toExceptionContext($oldContext): ExceptionContext
|
|
{
|
|
// Map old context structure to new unified context
|
|
return ExceptionContext::empty()
|
|
->withOperation(
|
|
$oldContext->exception->operation ?? 'unknown',
|
|
$oldContext->exception->component ?? 'unknown'
|
|
)
|
|
->withData($oldContext->exception->data ?? [])
|
|
->withDebug($oldContext->exception->debug ?? [])
|
|
->withMetadata($oldContext->exception->metadata ?? [])
|
|
->withRequestContext(
|
|
$oldContext->request->requestId ?? '',
|
|
$oldContext->request->method ?? 'GET',
|
|
$oldContext->request->requestUri ?? '/',
|
|
$oldContext->request->clientIp ?? null,
|
|
$oldContext->request->userAgent ?? null,
|
|
$oldContext->request->userId ?? null
|
|
)
|
|
->withSystemContext(
|
|
$oldContext->system->environment ?? 'production',
|
|
$oldContext->system->hostname ?? 'unknown',
|
|
$oldContext->system->memoryUsageMb ?? 0.0,
|
|
$oldContext->system->cpuUsage ?? 0.0
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Deprecation Notice**: `src/Framework/ErrorHandling/ErrorHandlerContext.php`
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Framework\ErrorHandling;
|
|
|
|
/**
|
|
* @deprecated Use \App\Framework\Exception\Core\ExceptionContext instead
|
|
* Will be removed in next major version
|
|
*/
|
|
final readonly class ErrorHandlerContext
|
|
{
|
|
// Keep existing implementation for backward compatibility
|
|
// Add deprecation notice in constructor
|
|
|
|
public function __construct(...)
|
|
{
|
|
trigger_error(
|
|
'ErrorHandlerContext is deprecated. Use ExceptionContext instead.',
|
|
E_USER_DEPRECATED
|
|
);
|
|
|
|
// ... existing constructor logic
|
|
}
|
|
}
|
|
```
|
|
|
|
## 4. Performance Optimizations
|
|
|
|
### 4.1 Async Processing
|
|
|
|
**Queue Integration** for ErrorAggregation and ErrorReporting:
|
|
|
|
```php
|
|
// In ErrorHandler::handleException()
|
|
if ($this->enableAggregation && !$this->shouldSkipAggregation($exception)) {
|
|
// Queue aggregation instead of synchronous processing
|
|
$this->queue->push('error_aggregation', [
|
|
'exception_class' => get_class($exception),
|
|
'exception_data' => $exception->toArray(),
|
|
'context' => $context->toArray(),
|
|
]);
|
|
}
|
|
|
|
if ($this->enableReporting && !$this->shouldSkipReporting($exception)) {
|
|
// Queue reporting instead of synchronous processing
|
|
$this->queue->push('error_reporting', [
|
|
'exception_class' => get_class($exception),
|
|
'exception_data' => $exception->toArray(),
|
|
'context' => $context->toArray(),
|
|
]);
|
|
}
|
|
```
|
|
|
|
**Background Jobs**:
|
|
- `ProcessErrorAggregationJob` - Processes queued aggregation
|
|
- `ProcessErrorReportingJob` - Processes queued reports
|
|
- `CleanupExpiredErrorEventsJob` - Cleanup old error events
|
|
|
|
### 4.2 Caching Strategy
|
|
|
|
**Pattern Cache** (existing, enhanced):
|
|
```php
|
|
// Cache error patterns with fingerprint key
|
|
$cacheKey = 'error_pattern:' . $fingerprint;
|
|
$this->cache->remember($cacheKey, fn() => $pattern, Duration::fromHours(1));
|
|
```
|
|
|
|
**ErrorCode Severity Cache** (new):
|
|
```php
|
|
// Cache ErrorCode → ErrorSeverity mapping
|
|
private static array $severityCache = [];
|
|
|
|
public function getSeverity(): ErrorSeverity
|
|
{
|
|
if (isset(self::$severityCache[$this->value])) {
|
|
return self::$severityCache[$this->value];
|
|
}
|
|
|
|
$severity = match($this) { ... };
|
|
self::$severityCache[$this->value] = $severity;
|
|
|
|
return $severity;
|
|
}
|
|
```
|
|
|
|
### 4.3 Database Optimizations
|
|
|
|
**Batch Inserts** for error_events:
|
|
```php
|
|
// Batch insert instead of individual inserts
|
|
public function storeEventsBatch(array $events): void
|
|
{
|
|
$data = array_map(fn($event) => $event->toArray(), $events);
|
|
$this->db->table('error_events')->insert($data);
|
|
}
|
|
```
|
|
|
|
**Index Strategy**:
|
|
- Composite indexes for common query patterns
|
|
- Separate indexes for cleanup queries (by occurred_at)
|
|
- JSON column indexes for metadata searches (PostgreSQL)
|
|
|
|
**Partitioning** (future optimization):
|
|
- Partition error_events by occurred_at (monthly partitions)
|
|
- Automatic partition management
|
|
- Faster cleanup queries
|
|
|
|
## 5. Testing Strategy
|
|
|
|
### 5.1 Unit Tests
|
|
|
|
**Test Coverage Requirements**: ≥95% for all core components
|
|
|
|
**Priority Test Suites**:
|
|
|
|
1. **FrameworkExceptionTest.php**
|
|
- Factory methods
|
|
- Fluent API
|
|
- Context enrichment
|
|
- Severity mapping
|
|
- Serialization
|
|
|
|
2. **ExceptionContextTest.php**
|
|
- Factory methods
|
|
- Fluent API for all context types
|
|
- Data sanitization
|
|
- Serialization
|
|
|
|
3. **ErrorCodeTest.php**
|
|
- Severity mapping for all 100+ codes
|
|
- Category extraction
|
|
- Recovery hints
|
|
- Retry timing
|
|
|
|
4. **ErrorHandlerTest.php**
|
|
- Automatic aggregation
|
|
- Automatic reporting
|
|
- Skip flags
|
|
- Backward compatibility
|
|
|
|
5. **ErrorAggregatorTest.php**
|
|
- Pattern detection
|
|
- Fingerprinting
|
|
- Alert triggering
|
|
- Batch processing
|
|
|
|
6. **ErrorBoundaryIntegrationTest.php**
|
|
- BoundaryException extends FrameworkException
|
|
- Automatic integration
|
|
- Circuit breaker
|
|
|
|
### 5.2 Integration Tests
|
|
|
|
**End-to-End Error Flow**:
|
|
```php
|
|
it('handles exception end-to-end with automatic integration', function () {
|
|
// 1. Throw exception
|
|
$exception = FrameworkException::create(
|
|
ErrorCode::DB_QUERY_FAILED,
|
|
'Database query failed'
|
|
);
|
|
|
|
// 2. Error handler processes
|
|
$this->errorHandler->handleException($exception);
|
|
|
|
// 3. Verify aggregation triggered
|
|
expect($this->queue)->toHaveJob('error_aggregation');
|
|
|
|
// 4. Process aggregation job
|
|
$this->processQueue('error_aggregation');
|
|
|
|
// 5. Verify pattern created
|
|
$patterns = $this->errorAggregator->getActivePatterns();
|
|
expect($patterns)->toHaveCount(1);
|
|
expect($patterns[0]->errorCode)->toBe('DB002');
|
|
|
|
// 6. Verify reporting triggered
|
|
expect($this->queue)->toHaveJob('error_reporting');
|
|
|
|
// 7. Process reporting job
|
|
$this->processQueue('error_reporting');
|
|
|
|
// 8. Verify report created
|
|
$reports = $this->errorReporter->getRecentReports();
|
|
expect($reports)->toHaveCount(1);
|
|
});
|
|
```
|
|
|
|
### 5.3 Performance Tests
|
|
|
|
**Benchmarks**:
|
|
```php
|
|
it('creates exception in <1ms', function () {
|
|
$start = microtime(true);
|
|
|
|
$exception = FrameworkException::create(
|
|
ErrorCode::DB_QUERY_FAILED,
|
|
'Test exception'
|
|
);
|
|
|
|
$duration = (microtime(true) - $start) * 1000;
|
|
expect($duration)->toBeLessThan(1.0);
|
|
});
|
|
|
|
it('processes 1000 errors in <5 seconds', function () {
|
|
$start = microtime(true);
|
|
|
|
for ($i = 0; $i < 1000; $i++) {
|
|
$exception = FrameworkException::create(
|
|
ErrorCode::DB_QUERY_FAILED,
|
|
"Error {$i}"
|
|
);
|
|
$this->errorHandler->handleException($exception);
|
|
}
|
|
|
|
$duration = microtime(true) - $start;
|
|
expect($duration)->toBeLessThan(5.0);
|
|
});
|
|
```
|
|
|
|
## 6. Migration Checklist
|
|
|
|
### Phase 1: Core Infrastructure
|
|
- [ ] Create new Exception/ directory structure
|
|
- [ ] Implement enhanced FrameworkException
|
|
- [ ] Implement unified ExceptionContext
|
|
- [ ] Enhance ErrorCode with severity mapping
|
|
- [ ] Create ErrorSeverity enum
|
|
- [ ] Write unit tests for core components
|
|
|
|
### Phase 2: Handler Integration
|
|
- [ ] Implement integrated ErrorHandler
|
|
- [ ] Create ContextBuilder
|
|
- [ ] Create ResponseFactory
|
|
- [ ] Implement async queue integration
|
|
- [ ] Write integration tests
|
|
|
|
### Phase 3: Aggregation Integration
|
|
- [ ] Enhance ErrorAggregator with processException()
|
|
- [ ] Enhance ErrorEvent with fromException()
|
|
- [ ] Update ErrorPattern (no changes needed)
|
|
- [ ] Migrate database schema
|
|
- [ ] Write aggregation tests
|
|
|
|
### Phase 4: Reporting Integration
|
|
- [ ] Enhance ErrorReporter with reportException()
|
|
- [ ] Merge ErrorReport with ErrorEvent
|
|
- [ ] Update AnalyticsEngine
|
|
- [ ] Write reporting tests
|
|
|
|
### Phase 5: Boundaries Integration
|
|
- [ ] Create BoundaryException extending FrameworkException
|
|
- [ ] Update BoundaryTimeoutException
|
|
- [ ] Update BoundaryFailedException
|
|
- [ ] Update ErrorBoundary to use new exceptions
|
|
- [ ] Write boundaries integration tests
|
|
|
|
### Phase 6: Backward Compatibility
|
|
- [ ] Create ErrorHandlerContextAdapter
|
|
- [ ] Add deprecation notices
|
|
- [ ] Update documentation
|
|
- [ ] Create migration guide
|
|
|
|
### Phase 7: Cleanup
|
|
- [ ] Remove old ErrorHandling/ directory
|
|
- [ ] Remove old ErrorAggregation/ directory
|
|
- [ ] Remove old ErrorBoundaries/ directory
|
|
- [ ] Remove old ErrorReporting/ directory
|
|
- [ ] Update all imports across codebase
|
|
|
|
## 7. Success Metrics
|
|
|
|
### Before
|
|
- ❌ 4 separate error modules
|
|
- ❌ Manual integration required
|
|
- ❌ 3 database tables
|
|
- ❌ Fragmented context
|
|
- ❌ Inconsistent exception types
|
|
- ❌ ~2.000 lines across modules
|
|
|
|
### After
|
|
- ✅ 1 unified Exception module
|
|
- ✅ Automatic integration
|
|
- ✅ 2 database tables (consolidated)
|
|
- ✅ Unified ExceptionContext
|
|
- ✅ All exceptions extend FrameworkException
|
|
- ✅ ~2.500 lines (enhanced functionality)
|
|
- ✅ <1ms exception creation
|
|
- ✅ <5ms pattern detection (async)
|
|
- ✅ <10ms error reporting (async)
|
|
|
|
## 8. Next Steps
|
|
|
|
1. **Review & Approval** - Review this design document
|
|
2. **Migration Plan** - Create detailed step-by-step migration plan
|
|
3. **Prototyping** - Build proof-of-concept for critical components
|
|
4. **Testing Strategy** - Define comprehensive test suite
|
|
5. **Implementation** - Execute migration in phases
|
|
6. **Documentation** - Update all framework documentation
|
|
7. **Release** - Plan major version release
|
|
|
|
---
|
|
|
|
**Status**: Design Phase Complete ✅
|
|
**Next Document**: `ERROR-HANDLING-MIGRATION-PLAN.md`
|
|
**Estimated Implementation**: 3-5 days
|