src/EventSubscriber/CrmAiExceptionSubscriber.php line 39
<?phpdeclare(strict_types=1);namespace App\EventSubscriber;use Psr\Log\LoggerInterface;use Symfony\Component\DependencyInjection\Attribute\Autowire;use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Event\ExceptionEvent;use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;use Symfony\Component\HttpKernel\KernelEvents;use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;use Symfony\Component\Security\Core\Exception\AccessDeniedException;use Symfony\Component\Security\Core\User\UserInterface;final class CrmAiExceptionSubscriber implements EventSubscriberInterface{public function __construct(#[Autowire(service: 'monolog.logger.crm_ai')]private readonly LoggerInterface $crmAiLogger,private readonly TokenStorageInterface $tokenStorage,) {}/*** @return array<string, array{0: string, 1: int}>*/public static function getSubscribedEvents(): array{return [KernelEvents::EXCEPTION => ['onKernelException', 64],];}public function onKernelException(ExceptionEvent $event): void{$request = $event->getRequest();if (!$this->isCrmAiRequest($request)) {return;}$exception = $event->getThrowable();$diagnosticId = $this->createDiagnosticId();$statusCode = $this->resolveStatusCode($exception);$this->logException($request, $exception, $diagnosticId, $statusCode);$message = $statusCode === Response::HTTP_FORBIDDEN? 'Nie udało się uruchomić rozmowy z asystentem. Brak dostępu albo sesja/token bezpieczeństwa są nieaktualne.': 'Nie udało się wykonać akcji asystenta CRM.';$event->setResponse(new JsonResponse(['error' => $message . ' Kod diagnostyczny: ' . $diagnosticId . '.','diagnostic_id' => $diagnosticId,'diagnostic_reason' => $statusCode === Response::HTTP_FORBIDDEN ? 'access_denied' : 'crm_ai_exception','support_message' => 'Podaj ten kod administratorowi CRM.',], $statusCode));}private function isCrmAiRequest(Request $request): bool{$path = $request->getPathInfo();return str_starts_with($path, '/admin/ai/')|| str_starts_with($path, '/consultant/ai/');}private function resolveStatusCode(\Throwable $exception): int{if ($exception instanceof AccessDeniedException) {return Response::HTTP_FORBIDDEN;}if ($exception instanceof HttpExceptionInterface) {return $exception->getStatusCode();}return Response::HTTP_INTERNAL_SERVER_ERROR;}private function createDiagnosticId(): string{return 'crm-ai-exception-' . date('Ymd-His') . '-' . bin2hex(random_bytes(4));}private function logException(Request $request, \Throwable $exception, string $diagnosticId, int $statusCode): void{$user = $this->tokenStorage->getToken()?->getUser();$roles = $user instanceof UserInterface ? $user->getRoles() : [];$context = ['diagnostic_id' => $diagnosticId,'status_code' => $statusCode,'path' => $request->getPathInfo(),'route' => $request->attributes->get('_route'),'method' => $request->getMethod(),'user_id' => $user !== null && method_exists($user, 'getId') ? $user->getId() : null,'user_identifier' => $user instanceof UserInterface ? $user->getUserIdentifier() : null,'user_roles' => array_values(array_unique($roles)),'csrf_header_present' => $request->headers->has('X-CSRF-TOKEN'),'csrf_header_length' => strlen((string) $request->headers->get('X-CSRF-TOKEN', '')),'ajax_header' => $request->headers->get('X-Requested-With'),'client_ip' => $request->getClientIp(),'user_agent' => $request->headers->get('User-Agent'),'exception_class' => $exception::class,'exception_message' => $exception->getMessage(),'exception_file' => $exception->getFile(),'exception_line' => $exception->getLine(),];if ($statusCode >= Response::HTTP_INTERNAL_SERVER_ERROR) {$this->crmAiLogger->error('CRM AI request failed before controller response.', $context);return;}$this->crmAiLogger->warning('CRM AI request was blocked before controller response.', $context);}}