Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,338 @@
<?php
declare(strict_types=1);
namespace App\Framework\ErrorAggregation;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\ErrorAggregation\Storage\ErrorStorageInterface;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Queue;
/**
* Central error aggregation engine
* Collects, analyzes, and patterns errors for alerting and monitoring
*/
final readonly class ErrorAggregator
{
private const string PATTERN_CACHE_PREFIX = 'error_pattern:';
private const int PATTERN_CACHE_TTL = 3600; // 1 hour
public function __construct(
private ErrorStorageInterface $storage,
private Cache $cache,
private Clock $clock,
private Queue $alertQueue,
private ?Logger $logger = null,
private int $batchSize = 100,
private int $maxRetentionDays = 90,
) {
}
/**
* Processes a new error from ErrorHandlerContext
*/
public function processError(ErrorHandlerContext $context): void
{
try {
$errorEvent = ErrorEvent::fromErrorHandlerContext($context);
$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' => $event->id->toString(),
'fingerprint' => $event->getFingerprint(),
'pattern_id' => $pattern->id->toString(),
'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 = 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);
} else {
// Try to get from storage
$pattern = $this->storage->getPatternByFingerprint($fingerprint);
}
if ($pattern === null) {
// Create new pattern
$pattern = ErrorPattern::fromErrorEvent($event);
} else {
// Update existing pattern
$pattern = $pattern->withNewOccurrence($event);
}
// Store updated pattern
$this->storage->storePattern($pattern);
// Cache the pattern
$this->cache->set(CacheKey::fromString($cacheKey), $pattern->toArray(), Duration::fromSeconds(self::PATTERN_CACHE_TTL));
return $pattern;
}
/**
* Queues alert for pattern
*/
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(),
];
// Queue with priority based on urgency
$priority = match ($pattern->getAlertUrgency()) {
AlertUrgency::URGENT => 100,
AlertUrgency::HIGH => 75,
AlertUrgency::MEDIUM => 50,
AlertUrgency::LOW => 25,
};
$this->alertQueue->push('error_alerts', $alertData, $priority);
$this->logError("Alert queued", [
'pattern_id' => $pattern->id->toString(),
'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) {
$this->logger->info("[ErrorAggregator] {$message}", $context);
}
}
}