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,289 @@
<?php
declare(strict_types=1);
namespace App\Framework\ErrorAggregation;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Ulid\Ulid;
/**
* Represents a single error event for aggregation and analysis
*/
final readonly class ErrorEvent
{
public function __construct(
public Ulid $id,
public string $service,
public string $component,
public string $operation,
public ErrorCode $errorCode,
public string $errorMessage,
public ErrorSeverity $severity,
public \DateTimeImmutable $occurredAt,
public array $context = [],
public array $metadata = [],
public ?string $requestId = null,
public ?string $userId = null,
public ?string $clientIp = null,
public bool $isSecurityEvent = false,
public ?string $stackTrace = null,
public ?string $userAgent = null,
) {
}
/**
* Creates ErrorEvent from ErrorHandlerContext
*/
public static function fromErrorHandlerContext(ErrorHandlerContext $context): self
{
return new self(
id: Ulid::generate(),
service: self::extractServiceName($context),
component: $context->exception->component ?? 'unknown',
operation: $context->exception->operation ?? 'unknown',
errorCode: self::extractErrorCode($context),
errorMessage: self::extractUserMessage($context),
severity: self::determineSeverity($context),
occurredAt: new \DateTimeImmutable(),
context: $context->exception->data,
metadata: $context->exception->metadata,
requestId: $context->request->requestId,
userId: $context->request->userId ?? null,
clientIp: $context->request->clientIp,
isSecurityEvent: $context->exception->metadata['security_event'] ?? false,
stackTrace: self::extractStackTrace($context),
userAgent: $context->request->userAgent,
);
}
/**
* Converts to array for storage/transmission
*/
public function toArray(): array
{
return [
'id' => $this->id->toString(),
'service' => $this->service,
'component' => $this->component,
'operation' => $this->operation,
'error_code' => $this->errorCode->value,
'error_message' => $this->errorMessage,
'severity' => $this->severity->value,
'occurred_at' => $this->occurredAt->format('c'),
'context' => $this->context,
'metadata' => $this->metadata,
'request_id' => $this->requestId,
'user_id' => $this->userId,
'client_ip' => $this->clientIp,
'is_security_event' => $this->isSecurityEvent,
'stack_trace' => $this->stackTrace,
'user_agent' => $this->userAgent,
];
}
/**
* Creates from array (for deserialization)
*/
public static function fromArray(array $data): self
{
return new self(
id: Ulid::fromString($data['id']),
service: $data['service'],
component: $data['component'],
operation: $data['operation'],
errorCode: ErrorCode::from($data['error_code']),
errorMessage: $data['error_message'],
severity: ErrorSeverity::from($data['severity']),
occurredAt: new \DateTimeImmutable($data['occurred_at']),
context: $data['context'] ?? [],
metadata: $data['metadata'] ?? [],
requestId: $data['request_id'],
userId: $data['user_id'],
clientIp: $data['client_ip'],
isSecurityEvent: $data['is_security_event'] ?? false,
stackTrace: $data['stack_trace'],
userAgent: $data['user_agent'],
);
}
/**
* Gets fingerprint for grouping similar errors
*/
public function getFingerprint(): string
{
$components = [
$this->service,
$this->component,
$this->operation,
$this->errorCode->value,
// Normalize error message to group similar errors
$this->normalizeErrorMessage($this->errorMessage),
];
return hash('sha256', implode('|', $components));
}
/**
* Checks if this error should trigger an alert
*/
public function shouldTriggerAlert(): bool
{
// Critical and error severity always trigger alerts
if (in_array($this->severity, [ErrorSeverity::CRITICAL, ErrorSeverity::ERROR])) {
return true;
}
// Security events always trigger alerts
if ($this->isSecurityEvent) {
return true;
}
// Check metadata for explicit alert requirement
return $this->metadata['requires_alert'] ?? false;
}
/**
* Gets alert urgency level
*/
public function getAlertUrgency(): AlertUrgency
{
if ($this->severity === ErrorSeverity::CRITICAL || $this->isSecurityEvent) {
return AlertUrgency::URGENT;
}
if ($this->severity === ErrorSeverity::ERROR) {
return AlertUrgency::HIGH;
}
if ($this->severity === ErrorSeverity::WARNING) {
return AlertUrgency::MEDIUM;
}
return AlertUrgency::LOW;
}
private static function extractServiceName(ErrorHandlerContext $context): string
{
// Try to extract service from request URI
$uri = $context->request->requestUri;
if (str_starts_with($uri, '/api/')) {
return 'api';
}
if (str_starts_with($uri, '/admin/')) {
return 'admin';
}
// Extract from component if available
if ($context->exception->component) {
return strtolower($context->exception->component);
}
return 'web';
}
private static function extractErrorCode(ErrorHandlerContext $context): ErrorCode
{
// Try to get from exception metadata
if (isset($context->exception->metadata['error_code'])) {
return ErrorCode::from($context->exception->metadata['error_code']);
}
// Try to get from exception data
if (isset($context->exception->data['error_code'])) {
return ErrorCode::from($context->exception->data['error_code']);
}
// Fallback based on HTTP status
$httpStatus = $context->metadata['http_status'] ?? 500;
return match (true) {
$httpStatus >= 500 => ErrorCode::SYSTEM_RESOURCE_EXHAUSTED,
$httpStatus === 404 => ErrorCode::HTTP_NOT_FOUND,
$httpStatus === 401 => ErrorCode::AUTH_CREDENTIALS_INVALID,
$httpStatus === 403 => ErrorCode::AUTH_INSUFFICIENT_PRIVILEGES,
$httpStatus === 429 => ErrorCode::HTTP_RATE_LIMIT_EXCEEDED,
default => ErrorCode::SYSTEM_RESOURCE_EXHAUSTED,
};
}
private static function extractUserMessage(ErrorHandlerContext $context): string
{
// Try user_message first
if (isset($context->exception->data['user_message'])) {
return $context->exception->data['user_message'];
}
// Try exception_message
if (isset($context->exception->data['exception_message'])) {
return $context->exception->data['exception_message'];
}
// Fallback to operation and component
$operation = $context->exception->operation ?? 'unknown_operation';
$component = $context->exception->component ?? 'unknown_component';
return "Error in {$component} during {$operation}";
}
private static function determineSeverity(ErrorHandlerContext $context): ErrorSeverity
{
// Security events are always critical
if ($context->exception->metadata['security_event'] ?? false) {
return ErrorSeverity::CRITICAL;
}
// Check explicit severity in metadata
if (isset($context->exception->metadata['severity'])) {
return ErrorSeverity::tryFrom($context->exception->metadata['severity']) ?? ErrorSeverity::ERROR;
}
// Determine from HTTP status
$httpStatus = $context->metadata['http_status'] ?? 500;
return match (true) {
$httpStatus >= 500 => ErrorSeverity::ERROR,
$httpStatus >= 400 => ErrorSeverity::WARNING,
default => ErrorSeverity::INFO,
};
}
private static function extractStackTrace(ErrorHandlerContext $context): ?string
{
// Don't include stack traces for security events in production
if (($context->exception->metadata['security_event'] ?? false) && ! ($_ENV['APP_DEBUG'] ?? false)) {
return null;
}
// Extract from exception debug data
if (isset($context->exception->debug['stack_trace'])) {
return $context->exception->debug['stack_trace'];
}
return null;
}
private function normalizeErrorMessage(string $message): string
{
// Remove specific details to group similar errors
$normalized = $message;
// Remove file paths
$normalized = preg_replace('#/[^\s]+#', '/path/to/file', $normalized);
// Remove specific IDs/numbers
$normalized = preg_replace('#\b\d+\b#', 'N', $normalized);
// Remove timestamps
$normalized = preg_replace('#\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2}#', 'TIMESTAMP', $normalized);
// Remove ULIDs/UUIDs
$normalized = preg_replace('#[0-9A-HJ-NP-TV-Z]{26}#', 'ULID', $normalized);
$normalized = preg_replace('#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}#i', 'UUID', $normalized);
return $normalized;
}
}