Files
michaelschiemer/src/Framework/ErrorReporting/ErrorReport.php
Michael Schiemer 70e45fb56e fix(Discovery): Add comprehensive debug logging for router initialization
- Add initializer count logging in DiscoveryServiceBootstrapper
- Add route structure analysis in RouterSetup
- Add request parameter logging in HttpRouter
- Update PHP production config for better OPcache handling
- Fix various config and error handling improvements
2025-10-27 22:23:18 +01:00

411 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\ErrorReporting;
use App\Framework\Core\ValueObjects\Duration;
use DateTimeImmutable;
use Throwable;
/**
* Structured error report with comprehensive context
*/
final readonly class ErrorReport
{
public function __construct(
public string $id,
public DateTimeImmutable $timestamp,
public string $level,
public string $message,
public string $exception,
public string $file,
public int $line,
public string $trace,
public array $context,
public ?string $userId = null,
public ?string $sessionId = null,
public ?string $requestId = null,
public ?string $userAgent = null,
public ?string $ipAddress = null,
public ?string $route = null,
public ?string $method = null,
public ?array $requestData = null,
public ?Duration $executionTime = null,
public ?int $memoryUsage = null,
public array $tags = [],
public array $breadcrumbs = [],
public ?string $release = null,
public ?string $environment = null,
public array $serverInfo = [],
public array $customData = []
) {
}
/**
* Create from Throwable
*/
public static function fromThrowable(
Throwable $throwable,
string $level = 'error',
array $context = [],
?string $environment = null
): self {
return new self(
id: self::generateId(),
timestamp: new DateTimeImmutable(),
level: $level,
message: $throwable->getMessage(),
exception: $throwable::class,
file: $throwable->getFile(),
line: $throwable->getLine(),
trace: $throwable->getTraceAsString(),
context: $context,
environment: $environment ?? 'production',
serverInfo: self::getServerInfo()
);
}
/**
* Create from manual report
*/
public static function create(
string $level,
string $message,
array $context = [],
?Throwable $exception = null,
?string $environment = null
): self {
return new self(
id: self::generateId(),
timestamp: new DateTimeImmutable(),
level: $level,
message: $message,
exception: $exception?->class ?? 'N/A',
file: $exception?->getFile() ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'] ?? 'unknown',
line: $exception?->getLine() ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['line'] ?? 0,
trace: $exception?->getTraceAsString() ?? self::getCurrentTrace(),
context: $context,
environment: $environment ?? 'production',
serverInfo: self::getServerInfo()
);
}
/**
* Add user context
*/
public function withUser(string $userId, ?string $sessionId = null): self
{
return new self(
id: $this->id,
timestamp: $this->timestamp,
level: $this->level,
message: $this->message,
exception: $this->exception,
file: $this->file,
line: $this->line,
trace: $this->trace,
context: $this->context,
userId: $userId,
sessionId: $sessionId ?? $this->sessionId,
requestId: $this->requestId,
userAgent: $this->userAgent,
ipAddress: $this->ipAddress,
route: $this->route,
method: $this->method,
requestData: $this->requestData,
executionTime: $this->executionTime,
memoryUsage: $this->memoryUsage,
tags: $this->tags,
breadcrumbs: $this->breadcrumbs,
release: $this->release,
environment: $this->environment,
serverInfo: $this->serverInfo,
customData: $this->customData
);
}
/**
* Add HTTP request context
*/
public function withRequest(
string $method,
string $route,
?string $requestId = null,
?string $userAgent = null,
?string $ipAddress = null,
?array $requestData = null
): self {
return new self(
id: $this->id,
timestamp: $this->timestamp,
level: $this->level,
message: $this->message,
exception: $this->exception,
file: $this->file,
line: $this->line,
trace: $this->trace,
context: $this->context,
userId: $this->userId,
sessionId: $this->sessionId,
requestId: $requestId,
userAgent: $userAgent,
ipAddress: $ipAddress,
route: $route,
method: $method,
requestData: $requestData,
executionTime: $this->executionTime,
memoryUsage: $this->memoryUsage,
tags: $this->tags,
breadcrumbs: $this->breadcrumbs,
release: $this->release,
environment: $this->environment,
serverInfo: $this->serverInfo,
customData: $this->customData
);
}
/**
* Add performance context
*/
public function withPerformance(Duration $executionTime, int $memoryUsage): self
{
return new self(
id: $this->id,
timestamp: $this->timestamp,
level: $this->level,
message: $this->message,
exception: $this->exception,
file: $this->file,
line: $this->line,
trace: $this->trace,
context: $this->context,
userId: $this->userId,
sessionId: $this->sessionId,
requestId: $this->requestId,
userAgent: $this->userAgent,
ipAddress: $this->ipAddress,
route: $this->route,
method: $this->method,
requestData: $this->requestData,
executionTime: $executionTime,
memoryUsage: $memoryUsage,
tags: $this->tags,
breadcrumbs: $this->breadcrumbs,
release: $this->release,
environment: $this->environment,
serverInfo: $this->serverInfo,
customData: $this->customData
);
}
/**
* Add tags
*/
public function withTags(array $tags): self
{
return new self(
id: $this->id,
timestamp: $this->timestamp,
level: $this->level,
message: $this->message,
exception: $this->exception,
file: $this->file,
line: $this->line,
trace: $this->trace,
context: $this->context,
userId: $this->userId,
sessionId: $this->sessionId,
requestId: $this->requestId,
userAgent: $this->userAgent,
ipAddress: $this->ipAddress,
route: $this->route,
method: $this->method,
requestData: $this->requestData,
executionTime: $this->executionTime,
memoryUsage: $this->memoryUsage,
tags: array_unique(array_merge($this->tags, $tags)),
breadcrumbs: $this->breadcrumbs,
release: $this->release,
environment: $this->environment,
serverInfo: $this->serverInfo,
customData: $this->customData
);
}
/**
* Add breadcrumbs
*/
public function withBreadcrumbs(array $breadcrumbs): self
{
return new self(
id: $this->id,
timestamp: $this->timestamp,
level: $this->level,
message: $this->message,
exception: $this->exception,
file: $this->file,
line: $this->line,
trace: $this->trace,
context: $this->context,
userId: $this->userId,
sessionId: $this->sessionId,
requestId: $this->requestId,
userAgent: $this->userAgent,
ipAddress: $this->ipAddress,
route: $this->route,
method: $this->method,
requestData: $this->requestData,
executionTime: $this->executionTime,
memoryUsage: $this->memoryUsage,
tags: $this->tags,
breadcrumbs: array_merge($this->breadcrumbs, $breadcrumbs),
release: $this->release,
environment: $this->environment,
serverInfo: $this->serverInfo,
customData: $this->customData
);
}
/**
* Add custom data
*/
public function withCustomData(array $customData): self
{
return new self(
id: $this->id,
timestamp: $this->timestamp,
level: $this->level,
message: $this->message,
exception: $this->exception,
file: $this->file,
line: $this->line,
trace: $this->trace,
context: $this->context,
userId: $this->userId,
sessionId: $this->sessionId,
requestId: $this->requestId,
userAgent: $this->userAgent,
ipAddress: $this->ipAddress,
route: $this->route,
method: $this->method,
requestData: $this->requestData,
executionTime: $this->executionTime,
memoryUsage: $this->memoryUsage,
tags: $this->tags,
breadcrumbs: $this->breadcrumbs,
release: $this->release,
environment: $this->environment,
serverInfo: $this->serverInfo,
customData: array_merge($this->customData, $customData)
);
}
/**
* Get severity level as integer
*/
public function getSeverityLevel(): int
{
return match (strtolower($this->level)) {
'emergency' => 0,
'alert' => 1,
'critical' => 2,
'error' => 3,
'warning' => 4,
'notice' => 5,
'info' => 6,
'debug' => 7,
default => 3,
};
}
/**
* Check if error is critical
*/
public function isCritical(): bool
{
return $this->getSeverityLevel() <= 2;
}
/**
* Get fingerprint for grouping similar errors
*/
public function getFingerprint(): string
{
$key = $this->exception . '|' . $this->file . '|' . $this->line;
return hash('xxh64', $key);
}
/**
* Convert to array for storage/transmission
*/
public function toArray(): array
{
return [
'id' => $this->id,
'timestamp' => $this->timestamp->format('c'),
'level' => $this->level,
'message' => $this->message,
'exception' => $this->exception,
'file' => $this->file,
'line' => $this->line,
'trace' => $this->trace,
'context' => $this->context,
'user_id' => $this->userId,
'session_id' => $this->sessionId,
'request_id' => $this->requestId,
'user_agent' => $this->userAgent,
'ip_address' => $this->ipAddress,
'route' => $this->route,
'method' => $this->method,
'request_data' => $this->requestData,
'execution_time' => $this->executionTime?->toMilliseconds(),
'memory_usage' => $this->memoryUsage,
'tags' => $this->tags,
'breadcrumbs' => $this->breadcrumbs,
'release' => $this->release,
'environment' => $this->environment,
'server_info' => $this->serverInfo,
'custom_data' => $this->customData,
'fingerprint' => $this->getFingerprint(),
'severity_level' => $this->getSeverityLevel(),
];
}
private static function generateId(): string
{
return bin2hex(random_bytes(16));
}
private static function getCurrentTrace(): string
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
array_shift($trace); // Remove current method
$result = [];
foreach ($trace as $frame) {
$file = $frame['file'] ?? 'unknown';
$line = $frame['line'] ?? 0;
$function = $frame['function'] ?? 'unknown';
$class = isset($frame['class']) ? $frame['class'] . '::' : '';
$result[] = "#{$file}({$line}): {$class}{$function}()";
}
return implode("\n", $result);
}
private static function getServerInfo(): array
{
return [
'php_version' => PHP_VERSION,
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown',
'server_name' => $_SERVER['SERVER_NAME'] ?? 'unknown',
'request_time' => $_SERVER['REQUEST_TIME'] ?? time(),
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
];
}
}