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); } }