clock); $this->processErrorEvent($errorEvent); } catch (\Throwable $e) { // Don't let error aggregation break the application $this->logError("Failed to process error: " . $e->getMessage(), [ 'exception' => $e, 'context' => $context->toArray(), ]); } } /** * Processes an ErrorEvent */ public function processErrorEvent(ErrorEvent $event): void { // Store the individual error event $this->storage->storeEvent($event); // Update or create error pattern $pattern = $this->updateErrorPattern($event); // Check if pattern should trigger alert if ($pattern->shouldAlert()) { $this->queueAlert($pattern, $event); } // Log for debugging $this->logError("Error processed", [ 'event_id' => (string) $event->id, 'fingerprint' => $event->getFingerprint(), 'pattern_id' => (string) $pattern->id, 'occurrence_count' => $pattern->occurrenceCount, 'should_alert' => $pattern->shouldAlert(), ]); } /** * Updates or creates error pattern for the event */ private function updateErrorPattern(ErrorEvent $event): ErrorPattern { $fingerprint = $event->getFingerprint(); $cacheKey = CacheKey::fromString(self::PATTERN_CACHE_PREFIX . $fingerprint); // Try to get existing pattern from cache $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); } if ($pattern === null) { // Create new pattern $pattern = ErrorPattern::fromErrorEvent($event, $this->clock); } else { // Update existing pattern $pattern = $pattern->withNewOccurrence($event); } // Store updated pattern $this->storage->storePattern($pattern); // Cache the pattern $cacheItem = \App\Framework\Cache\CacheItem::forSet( $cacheKey, $pattern->toArray(), Duration::fromSeconds(self::PATTERN_CACHE_TTL) ); $this->cache->set($cacheItem); return $pattern; } /** * Queues alert for pattern */ private function queueAlert(ErrorPattern $pattern, ErrorEvent $triggeringEvent): void { // 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() ); // 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(), }; // 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' => (string) $pattern->id, 'urgency' => $pattern->getAlertUrgency()->value, 'occurrence_count' => $pattern->occurrenceCount, ]); } /** * Gets error statistics for a time period */ public function getStatistics(\DateTimeImmutable $from, \DateTimeImmutable $to): array { return $this->storage->getStatistics($from, $to); } /** * Gets active error patterns */ public function getActivePatterns(int $limit = 50, int $offset = 0): array { return $this->storage->getActivePatterns($limit, $offset); } /** * Gets error patterns by service */ public function getPatternsByService(string $service, int $limit = 50): array { return $this->storage->getPatternsByService($service, $limit); } /** * Gets recent error events */ public function getRecentEvents(int $limit = 100, ?ErrorSeverity $severity = null): array { return $this->storage->getRecentEvents($limit, $severity); } /** * Acknowledges an error pattern */ public function acknowledgePattern(string $patternId, string $acknowledgedBy, ?string $resolution = null): bool { $pattern = $this->storage->getPatternById($patternId); if ($pattern === null) { return false; } $acknowledgedPattern = $pattern->acknowledge($acknowledgedBy, $resolution); $this->storage->storePattern($acknowledgedPattern); // Clear from cache $cacheKey = self::PATTERN_CACHE_PREFIX . $pattern->fingerprint; $this->cache->delete(CacheKey::fromString($cacheKey)); $this->logError("Pattern acknowledged", [ 'pattern_id' => $patternId, 'acknowledged_by' => $acknowledgedBy, 'resolution' => $resolution, ]); return true; } /** * Resolves an error pattern */ public function resolvePattern(string $patternId, string $resolution): bool { $pattern = $this->storage->getPatternById($patternId); if ($pattern === null) { return false; } $resolvedPattern = $pattern->resolve($resolution); $this->storage->storePattern($resolvedPattern); // Clear from cache $cacheKey = self::PATTERN_CACHE_PREFIX . $pattern->fingerprint; $this->cache->delete(CacheKey::fromString($cacheKey)); $this->logError("Pattern resolved", [ 'pattern_id' => $patternId, 'resolution' => $resolution, ]); return true; } /** * Processes errors in batch for performance */ public function processBatch(array $errorEvents): void { $batches = array_chunk($errorEvents, $this->batchSize); foreach ($batches as $batch) { $this->storage->storeEventsBatch($batch); foreach ($batch as $event) { $this->updateErrorPattern($event); } } $this->logError("Batch processed", [ 'total_events' => count($errorEvents), 'batches' => count($batches), ]); } /** * Cleans up old data based on retention policy */ public function cleanup(): int { $deletedEvents = 0; $deletedPatterns = 0; foreach (ErrorSeverity::cases() as $severity) { $retentionDays = $severity->getRetentionDays(); $cutoffDate = $this->clock->now()->sub(new \DateInterval("P{$retentionDays}D")); $deletedEvents += $this->storage->deleteOldEvents($cutoffDate, $severity); } // Delete inactive patterns older than max retention $maxCutoff = $this->clock->now()->sub(new \DateInterval("P{$this->maxRetentionDays}D")); $deletedPatterns = $this->storage->deleteOldPatterns($maxCutoff); $this->logError("Cleanup completed", [ 'deleted_events' => $deletedEvents, 'deleted_patterns' => $deletedPatterns, ]); return $deletedEvents + $deletedPatterns; } /** * Gets error trends for analysis */ public function getErrorTrends( \DateTimeImmutable $from, \DateTimeImmutable $to, string $groupBy = 'hour' ): array { return $this->storage->getErrorTrends($from, $to, $groupBy); } /** * Gets top error patterns by occurrence count */ public function getTopPatterns(int $limit = 10, ?string $service = null): array { return $this->storage->getTopPatterns($limit, $service); } /** * Exports error data for external analysis */ public function exportData( \DateTimeImmutable $from, \DateTimeImmutable $to, array $filters = [] ): \Generator { return $this->storage->exportEvents($from, $to, $filters); } /** * Gets health status of error aggregation system */ public function getHealthStatus(): array { try { $stats = $this->getStatistics( $this->clock->now()->sub(new \DateInterval('PT1H')), $this->clock->now() ); return [ 'status' => 'healthy', 'last_hour_events' => $stats['total_events'] ?? 0, 'active_patterns' => count($this->getActivePatterns(100)), 'storage_health' => $this->storage->getHealthStatus(), 'cache_status' => $this->cache->get(CacheKey::fromString('health_check')) !== null ? 'healthy' : 'degraded', ]; } catch (\Throwable $e) { return [ 'status' => 'unhealthy', 'error' => $e->getMessage(), ]; } } private function logError(string $message, array $context = []): void { if ($this->logger) { $logContext = !empty($context) ? \App\Framework\Logging\ValueObjects\LogContext::withData($context) : null; $this->logger->info("[ErrorAggregator] {$message}", $logContext); } } }