<?php
namespace Sq\Service\EventSubscriber;
use Sq\Entity\Apm\WarningError;
use Sq\Event\Kernel\InitialiseContainerEvent;
use Sq\Exception\Controller\AdminAccessDeniedException;
use Sq\Exception\Controller\MemberAccessDeniedException;
use Sq\Exception\Controller\MFARequiredException;
use Sq\Exception\Controller\MFAVerificationException;
use Sq\Service\Apm\ApmInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
class ApmErrorSubscriber implements EventSubscriberInterface
{
private ApmInterface $apm;
protected array $ignoredExceptionClasses = [
MFARequiredException::class,
MFAVerificationException::class,
AdminAccessDeniedException::class,
MemberAccessDeniedException::class,
NotFoundHttpException::class
];
protected array $trackedErrors = [];
public function __construct(ApmInterface $apm)
{
$this->apm = $apm;
}
/** @inheritDoc */
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => ['onKernelException'],
InitialiseContainerEvent::class => ['onInitializeContainer'],
];
}
public function onInitializeContainer()
{
if (!$this->apm->isTracking())
{
return;
}
$this->registerErrorHandler();
$this->registerExceptionHandler();
}
public function onKernelException(ExceptionEvent $event)
{
if (!$this->apm->isTracking())
{
return;
}
$this->trackApmError($event->getThrowable());
}
protected function registerErrorHandler()
{
// TODO: Remove $error_context once we migrate to PHP 8
$prevErrorHandler = \set_error_handler(function ($phpErrorLevel, $message, $filename, $line, $errorContext = []) use (&$prevErrorHandler)
{
switch ($phpErrorLevel)
{
case E_WARNING:
case E_USER_WARNING:
$this->trackApmError(new WarningError($message, 0, $phpErrorLevel, $filename, $line));
}
return $prevErrorHandler ? $prevErrorHandler($phpErrorLevel, $message, $filename, $line) : null;
}, E_ALL);
}
protected function registerExceptionHandler()
{
$prevExceptionHandler = \set_exception_handler(function ($exception) use (&$prevExceptionHandler): void
{
$this->trackApmError($exception);
if ($prevExceptionHandler)
{
$prevExceptionHandler($exception);
}
});
}
protected function trackApmError(\Throwable $throwable)
{
if (
in_array(get_class($throwable), $this->ignoredExceptionClasses) ||
isset($this->trackedErrors[spl_object_hash($throwable)])
) {
return;
}
$this->trackedErrors[spl_object_hash($throwable)] = $throwable;
$this->apm->noticeErrorFromThrowable($throwable);
}
}