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:
278
src/Framework/ErrorReporting/ErrorReporter.php
Normal file
278
src/Framework/ErrorReporting/ErrorReporter.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\ErrorReporting;
|
||||
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\ErrorReporting\Storage\ErrorReportStorageInterface;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Queue\Queue;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Central error reporting service
|
||||
*/
|
||||
final readonly class ErrorReporter
|
||||
{
|
||||
public function __construct(
|
||||
private ErrorReportStorageInterface $storage,
|
||||
private Clock $clock,
|
||||
private ?Logger $logger = null,
|
||||
private ?Queue $queue = null,
|
||||
private bool $asyncProcessing = true,
|
||||
private array $processors = [],
|
||||
private array $filters = []
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error from Throwable
|
||||
*/
|
||||
public function reportThrowable(
|
||||
Throwable $throwable,
|
||||
string $level = 'error',
|
||||
array $context = []
|
||||
): string {
|
||||
$report = ErrorReport::fromThrowable($throwable, $level, $context);
|
||||
|
||||
return $this->report($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a manual error
|
||||
*/
|
||||
public function reportError(
|
||||
string $level,
|
||||
string $message,
|
||||
array $context = [],
|
||||
?Throwable $exception = null
|
||||
): string {
|
||||
$report = ErrorReport::create($level, $message, $context, $exception);
|
||||
|
||||
return $this->report($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error with full context
|
||||
*/
|
||||
public function report(ErrorReport $report): string
|
||||
{
|
||||
try {
|
||||
// Apply filters - skip reporting if any filter returns false
|
||||
foreach ($this->filters as $filter) {
|
||||
if (! $filter($report)) {
|
||||
$this->logDebug("Error report filtered out", ['report_id' => $report->id]);
|
||||
|
||||
return $report->id;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply processors to enrich the report
|
||||
$enrichedReport = $this->applyProcessors($report);
|
||||
|
||||
// Store or queue the report
|
||||
if ($this->asyncProcessing && $this->queue) {
|
||||
$this->queueReport($enrichedReport);
|
||||
} else {
|
||||
$this->storeReport($enrichedReport);
|
||||
}
|
||||
|
||||
$this->logInfo("Error report created", [
|
||||
'report_id' => $enrichedReport->id,
|
||||
'level' => $enrichedReport->level,
|
||||
'exception' => $enrichedReport->exception,
|
||||
]);
|
||||
|
||||
return $enrichedReport->id;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Fallback logging if reporting system fails
|
||||
$this->logError("Failed to report error", [
|
||||
'original_message' => $report->message,
|
||||
'reporting_error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return $report->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report multiple errors in batch
|
||||
*/
|
||||
public function reportBatch(array $reports): array
|
||||
{
|
||||
$reportIds = [];
|
||||
|
||||
foreach ($reports as $report) {
|
||||
if ($report instanceof ErrorReport) {
|
||||
$reportIds[] = $this->report($report);
|
||||
} else {
|
||||
$this->logError("Invalid report in batch", ['report' => gettype($report)]);
|
||||
}
|
||||
}
|
||||
|
||||
return $reportIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add request context to current thread
|
||||
*/
|
||||
public function withRequestContext(
|
||||
string $method,
|
||||
string $route,
|
||||
?string $requestId = null,
|
||||
?string $userAgent = null,
|
||||
?string $ipAddress = null,
|
||||
?array $requestData = null
|
||||
): RequestContextualReporter {
|
||||
return new RequestContextualReporter(
|
||||
reporter: $this,
|
||||
method: $method,
|
||||
route: $route,
|
||||
requestId: $requestId,
|
||||
userAgent: $userAgent,
|
||||
ipAddress: $ipAddress,
|
||||
requestData: $requestData
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user context to current thread
|
||||
*/
|
||||
public function withUserContext(string $userId, ?string $sessionId = null): UserContextualReporter
|
||||
{
|
||||
return new UserContextualReporter(
|
||||
reporter: $this,
|
||||
userId: $userId,
|
||||
sessionId: $sessionId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error report by ID
|
||||
*/
|
||||
public function getReport(string $reportId): ?ErrorReport
|
||||
{
|
||||
try {
|
||||
return $this->storage->find($reportId);
|
||||
} catch (Throwable $e) {
|
||||
$this->logError("Failed to retrieve error report", [
|
||||
'report_id' => $reportId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent error reports
|
||||
*/
|
||||
public function getRecentReports(int $limit = 100, int $offset = 0): array
|
||||
{
|
||||
try {
|
||||
return $this->storage->findRecent($limit, $offset);
|
||||
} catch (Throwable $e) {
|
||||
$this->logError("Failed to retrieve recent error reports", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error reports by criteria
|
||||
*/
|
||||
public function findReports(ErrorReportCriteria $criteria): array
|
||||
{
|
||||
try {
|
||||
return $this->storage->findByCriteria($criteria);
|
||||
} catch (Throwable $e) {
|
||||
$this->logError("Failed to find error reports", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error statistics
|
||||
*/
|
||||
public function getStatistics(\DateTimeImmutable $from, \DateTimeImmutable $to): ErrorStatistics
|
||||
{
|
||||
try {
|
||||
return $this->storage->getStatistics($from, $to);
|
||||
} catch (Throwable $e) {
|
||||
$this->logError("Failed to get error statistics", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return ErrorStatistics::empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old error reports
|
||||
*/
|
||||
public function cleanup(\DateTimeImmutable $before): int
|
||||
{
|
||||
try {
|
||||
$deleted = $this->storage->deleteOlderThan($before);
|
||||
$this->logInfo("Cleaned up old error reports", ['deleted_count' => $deleted]);
|
||||
|
||||
return $deleted;
|
||||
} catch (Throwable $e) {
|
||||
$this->logError("Failed to cleanup error reports", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function applyProcessors(ErrorReport $report): ErrorReport
|
||||
{
|
||||
$processedReport = $report;
|
||||
|
||||
foreach ($this->processors as $processor) {
|
||||
try {
|
||||
$processedReport = $processor($processedReport);
|
||||
} catch (Throwable $e) {
|
||||
$this->logError("Error processor failed", [
|
||||
'processor' => $processor::class ?? 'closure',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $processedReport;
|
||||
}
|
||||
|
||||
private function storeReport(ErrorReport $report): void
|
||||
{
|
||||
$this->storage->store($report);
|
||||
}
|
||||
|
||||
private function queueReport(ErrorReport $report): void
|
||||
{
|
||||
$this->queue->push('error_report_processing', $report->toArray());
|
||||
}
|
||||
|
||||
private function logDebug(string $message, array $context = []): void
|
||||
{
|
||||
$this->logger?->debug("[ErrorReporter] {$message}", $context);
|
||||
}
|
||||
|
||||
private function logInfo(string $message, array $context = []): void
|
||||
{
|
||||
$this->logger?->info("[ErrorReporter] {$message}", $context);
|
||||
}
|
||||
|
||||
private function logError(string $message, array $context = []): void
|
||||
{
|
||||
$this->logger?->error("[ErrorReporter] {$message}", $context);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user