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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -17,7 +17,7 @@ use App\Framework\Queue\Queue;
* Central error aggregation engine
* Collects, analyzes, and patterns errors for alerting and monitoring
*/
final readonly class ErrorAggregator
final readonly class ErrorAggregator implements ErrorAggregatorInterface
{
private const string PATTERN_CACHE_PREFIX = 'error_pattern:';
private const int PATTERN_CACHE_TTL = 3600; // 1 hour
@@ -39,7 +39,7 @@ final readonly class ErrorAggregator
public function processError(ErrorHandlerContext $context): void
{
try {
$errorEvent = ErrorEvent::fromErrorHandlerContext($context);
$errorEvent = ErrorEvent::fromErrorHandlerContext($context, $this->clock);
$this->processErrorEvent($errorEvent);
} catch (\Throwable $e) {
// Don't let error aggregation break the application
@@ -68,9 +68,9 @@ final readonly class ErrorAggregator
// Log for debugging
$this->logError("Error processed", [
'event_id' => $event->id->toString(),
'event_id' => (string) $event->id,
'fingerprint' => $event->getFingerprint(),
'pattern_id' => $pattern->id->toString(),
'pattern_id' => (string) $pattern->id,
'occurrence_count' => $pattern->occurrenceCount,
'should_alert' => $pattern->shouldAlert(),
]);
@@ -82,12 +82,13 @@ final readonly class ErrorAggregator
private function updateErrorPattern(ErrorEvent $event): ErrorPattern
{
$fingerprint = $event->getFingerprint();
$cacheKey = self::PATTERN_CACHE_PREFIX . $fingerprint;
$cacheKey = CacheKey::fromString(self::PATTERN_CACHE_PREFIX . $fingerprint);
// Try to get existing pattern from cache
$cachedPattern = $this->cache->get(CacheKey::fromString($cacheKey));
if ($cachedPattern) {
$pattern = ErrorPattern::fromArray($cachedPattern);
$cacheResult = $this->cache->get($cacheKey);
if ($cacheResult->isHit) {
// Cache returns the pattern array, we need to deserialize it
$pattern = ErrorPattern::fromArray($cacheResult->value);
} else {
// Try to get from storage
$pattern = $this->storage->getPatternByFingerprint($fingerprint);
@@ -95,7 +96,7 @@ final readonly class ErrorAggregator
if ($pattern === null) {
// Create new pattern
$pattern = ErrorPattern::fromErrorEvent($event);
$pattern = ErrorPattern::fromErrorEvent($event, $this->clock);
} else {
// Update existing pattern
$pattern = $pattern->withNewOccurrence($event);
@@ -105,7 +106,12 @@ final readonly class ErrorAggregator
$this->storage->storePattern($pattern);
// Cache the pattern
$this->cache->set(CacheKey::fromString($cacheKey), $pattern->toArray(), Duration::fromSeconds(self::PATTERN_CACHE_TTL));
$cacheItem = \App\Framework\Cache\CacheItem::forSet(
$cacheKey,
$pattern->toArray(),
Duration::fromSeconds(self::PATTERN_CACHE_TTL)
);
$this->cache->set($cacheItem);
return $pattern;
}
@@ -115,28 +121,33 @@ final readonly class ErrorAggregator
*/
private function queueAlert(ErrorPattern $pattern, ErrorEvent $triggeringEvent): void
{
$alertData = [
'type' => 'error_pattern_alert',
'pattern_id' => $pattern->id->toString(),
'event_id' => $triggeringEvent->id->toString(),
'urgency' => $pattern->getAlertUrgency()->value,
'created_at' => $this->clock->now()->format('c'),
'pattern_data' => $pattern->toArray(),
'triggering_event' => $triggeringEvent->toArray(),
];
// Create alert job
$alertJob = new \App\Framework\ErrorAggregation\Jobs\ErrorPatternAlertJob(
patternId: (string) $pattern->id,
eventId: (string) $triggeringEvent->id,
urgency: $pattern->getAlertUrgency(),
patternData: $pattern->toArray(),
triggeringEventData: $triggeringEvent->toArray()
);
// Queue with priority based on urgency
$priority = match ($pattern->getAlertUrgency()) {
AlertUrgency::URGENT => 100,
AlertUrgency::HIGH => 75,
AlertUrgency::MEDIUM => 50,
AlertUrgency::LOW => 25,
// Map urgency to queue priority
$queuePriority = match ($pattern->getAlertUrgency()) {
AlertUrgency::URGENT => \App\Framework\Queue\ValueObjects\QueuePriority::critical(),
AlertUrgency::HIGH => \App\Framework\Queue\ValueObjects\QueuePriority::high(),
AlertUrgency::MEDIUM => \App\Framework\Queue\ValueObjects\QueuePriority::normal(),
AlertUrgency::LOW => \App\Framework\Queue\ValueObjects\QueuePriority::low(),
};
$this->alertQueue->push('error_alerts', $alertData, $priority);
// Create job payload and push to queue
$payload = \App\Framework\Queue\ValueObjects\JobPayload::create(
job: $alertJob,
priority: $queuePriority
);
$this->alertQueue->push($payload);
$this->logError("Alert queued", [
'pattern_id' => $pattern->id->toString(),
'pattern_id' => (string) $pattern->id,
'urgency' => $pattern->getAlertUrgency()->value,
'occurrence_count' => $pattern->occurrenceCount,
]);
@@ -332,7 +343,10 @@ final readonly class ErrorAggregator
private function logError(string $message, array $context = []): void
{
if ($this->logger) {
$this->logger->info("[ErrorAggregator] {$message}", $context);
$logContext = !empty($context)
? \App\Framework\Logging\ValueObjects\LogContext::withData($context)
: null;
$this->logger->info("[ErrorAggregator] {$message}", $logContext);
}
}
}