- 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
411 lines
13 KiB
PHP
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'),
|
|
];
|
|
}
|
|
}
|