src/Shared/Infrastructure/EventSubscriber/RequestPerfSubscriber.php line 50

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Shared\Infrastructure\EventSubscriber;
  4. use App\Shared\Infrastructure\Observability\ObservabilityLogger;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. use Symfony\Component\HttpKernel\Event\RequestEvent;
  7. use Symfony\Component\HttpKernel\Event\TerminateEvent;
  8. use Symfony\Component\HttpKernel\KernelEvents;
  9. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  10. use Throwable;
  11. /**
  12.  * FIX-260526-010 — [OBS-PERF-TRAMITE] medición wall-clock por request HTTP entrante.
  13.  *
  14.  * Captura t0 en kernel.request y dt total en kernel.terminate. Si dt > umbral
  15.  * (3.0s por defecto) emite warning permanente al canal de auditoría con
  16.  * {route, user, dt_ms}. Pensado para correlacionar con [OBS-PERF-HTTP] y
  17.  * detectar la operación concreta que dispara la espera de 5 minutos reportada
  18.  * por Enrique.
  19.  *
  20.  * Garantías:
  21.  *  - Sólo lectura de Request; ningún side-effect en flujo funcional.
  22.  *  - Logger envuelto en try/catch (`safeLog`); errores no propagan.
  23.  *  - Sólo loggea sub-requests `main` (ignora ESI/fragments).
  24.  */
  25. final class RequestPerfSubscriber implements EventSubscriberInterface
  26. {
  27.     private const ATTR_START_TIME '_obs_perf_t0';
  28.     private float $thresholdSeconds;
  29.     public function __construct(
  30.         private ObservabilityLogger $observabilityLogger,
  31.         private ?TokenStorageInterface $tokenStorage null,
  32.         float $thresholdSeconds 3.0
  33.     ) {
  34.         $this->thresholdSeconds $thresholdSeconds;
  35.     }
  36.     public static function getSubscribedEvents(): array
  37.     {
  38.         return [
  39.             KernelEvents::REQUEST   => ['onKernelRequest'1024], // prioridad alta para capturar t0 cuanto antes
  40.             KernelEvents::TERMINATE => ['onKernelTerminate', -1024],
  41.         ];
  42.     }
  43.     public function onKernelRequest(RequestEvent $event): void
  44.     {
  45.         if (!$event->isMainRequest()) {
  46.             return;
  47.         }
  48.         try {
  49.             $event->getRequest()->attributes->set(self::ATTR_START_TIMEmicrotime(true));
  50.         } catch (Throwable $e) {
  51.             // no-op: observabilidad no rompe flujo.
  52.         }
  53.     }
  54.     public function onKernelTerminate(TerminateEvent $event): void
  55.     {
  56.         try {
  57.             $request $event->getRequest();
  58.             $t0 $request->attributes->get(self::ATTR_START_TIME);
  59.             if (!is_float($t0)) {
  60.                 return;
  61.             }
  62.             $dt microtime(true) - $t0;
  63.             if ($dt $this->thresholdSeconds) {
  64.                 return;
  65.             }
  66.             $context = [
  67.                 'route'    => $request->attributes->get('_route'),
  68.                 'method'   => $request->getMethod(),
  69.                 'path'     => $request->getPathInfo(),
  70.                 'user'     => $this->resolveUser(),
  71.                 'dt_ms'    => (int) round($dt 1000),
  72.                 'status'   => $event->getResponse()->getStatusCode(),
  73.             ];
  74.             $this->observabilityLogger->safeLog('warning''[OBS-PERF-TRAMITE] slow_request'$context);
  75.         } catch (Throwable $e) {
  76.             // swallow.
  77.         }
  78.     }
  79.     private function resolveUser(): ?string
  80.     {
  81.         try {
  82.             if ($this->tokenStorage === null) {
  83.                 return null;
  84.             }
  85.             $token $this->tokenStorage->getToken();
  86.             if ($token === null) {
  87.                 return null;
  88.             }
  89.             return method_exists($token'getUserIdentifier') ? $token->getUserIdentifier() : (string) $token->getUser();
  90.         } catch (Throwable $e) {
  91.             return null;
  92.         }
  93.     }
  94. }