vendor/sentry/sentry-symfony/src/EventListener/MessengerListener.php line 117

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sentry\SentryBundle\EventListener;
  4. use Sentry\Event;
  5. use Sentry\EventHint;
  6. use Sentry\ExceptionMechanism;
  7. use Sentry\State\HubInterface;
  8. use Sentry\State\Scope;
  9. use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
  10. use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
  11. use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
  12. use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
  13. use Symfony\Component\Messenger\Exception\HandlerFailedException;
  14. use Symfony\Component\Messenger\Exception\WrappedExceptionsInterface;
  15. use Symfony\Component\Messenger\Stamp\BusNameStamp;
  16. final class MessengerListener
  17. {
  18.     /**
  19.      * @var HubInterface The current hub
  20.      */
  21.     private $hub;
  22.     /**
  23.      * @var bool Whether to capture errors thrown while processing a message that
  24.      *           will be retried
  25.      */
  26.     private $captureSoftFails;
  27.     /**
  28.      * @var bool When this is enabled, a new scope is pushed on the stack when a message
  29.      *           is received and will pop it again after the message was finished (either success or fail).
  30.      *           This allows us to have breadcrumbs only for one message and no breadcrumb is leaked into
  31.      *           future messages.
  32.      */
  33.     private $isolateBreadcrumbsByMessage;
  34.     /**
  35.      * @param HubInterface $hub                         The current hub
  36.      * @param bool         $captureSoftFails            Whether to capture errors thrown
  37.      *                                                  while processing a message that
  38.      *                                                  will be retried
  39.      * @param bool         $isolateBreadcrumbsByMessage Whether messages should have isolated breadcrumbs
  40.      */
  41.     public function __construct(HubInterface $hubbool $captureSoftFails truebool $isolateBreadcrumbsByMessage false)
  42.     {
  43.         $this->hub $hub;
  44.         $this->captureSoftFails $captureSoftFails;
  45.         $this->isolateBreadcrumbsByMessage $isolateBreadcrumbsByMessage;
  46.     }
  47.     /**
  48.      * This method is called for each message that failed to be handled.
  49.      *
  50.      * @param WorkerMessageFailedEvent $event The event
  51.      */
  52.     public function handleWorkerMessageFailedEvent(WorkerMessageFailedEvent $event): void
  53.     {
  54.         try {
  55.             if (!$this->captureSoftFails && $event->willRetry()) {
  56.                 return;
  57.             }
  58.             $this->hub->withScope(function (Scope $scope) use ($event): void {
  59.                 $envelope $event->getEnvelope();
  60.                 $exception $event->getThrowable();
  61.                 $scope->setTag('messenger.receiver_name'$event->getReceiverName());
  62.                 $scope->setTag('messenger.message_class'\get_class($envelope->getMessage()));
  63.                 /** @var BusNameStamp|null $messageBusStamp */
  64.                 $messageBusStamp $envelope->last(BusNameStamp::class);
  65.                 if (null !== $messageBusStamp) {
  66.                     $scope->setTag('messenger.message_bus'$messageBusStamp->getBusName());
  67.                 }
  68.                 $this->captureException($exception$event->willRetry());
  69.             });
  70.             $this->flushClient();
  71.         } finally {
  72.             // We always want to pop the scope at the end of this method to add the breadcrumbs
  73.             // to any potential event that is produced.
  74.             if ($this->isolateBreadcrumbsByMessage) {
  75.                 $this->hub->popScope();
  76.             }
  77.         }
  78.     }
  79.     /**
  80.      * This method is called for each handled message.
  81.      *
  82.      * @param WorkerMessageHandledEvent $event The event
  83.      */
  84.     public function handleWorkerMessageHandledEvent(WorkerMessageHandledEvent $event): void
  85.     {
  86.         // Flush normally happens at shutdown... which only happens in the worker if it is run with a lifecycle limit
  87.         // such as --time=X or --limit=Y. Flush immediately in a background worker.
  88.         $this->flushClient();
  89.         if ($this->isolateBreadcrumbsByMessage) {
  90.             $this->hub->popScope();
  91.         }
  92.     }
  93.     /**
  94.      * Method that will push a new scope on the hub to create message local breadcrumbs that will not
  95.      * "leak" into future messages.
  96.      *
  97.      * @param WorkerMessageReceivedEvent $event
  98.      *
  99.      * @return void
  100.      */
  101.     public function handleWorkerMessageReceivedEvent(WorkerMessageReceivedEvent $event): void
  102.     {
  103.         if ($this->isolateBreadcrumbsByMessage) {
  104.             $this->hub->pushScope();
  105.         }
  106.     }
  107.     /**
  108.      * Creates Sentry events from the given exception.
  109.      *
  110.      * Unpacks multiple exceptions wrapped in a HandlerFailedException and notifies
  111.      * Sentry of each individual exception.
  112.      *
  113.      * If the message will be retried the exceptions will be marked as handled
  114.      * in Sentry.
  115.      */
  116.     private function captureException(\Throwable $exceptionbool $willRetry): void
  117.     {
  118.         if ($exception instanceof WrappedExceptionsInterface) {
  119.             $exception $exception->getWrappedExceptions();
  120.         } elseif ($exception instanceof HandlerFailedException && method_exists($exception'getNestedExceptions')) {
  121.             $exception $exception->getNestedExceptions();
  122.         } elseif ($exception instanceof DelayedMessageHandlingException && method_exists($exception'getExceptions')) {
  123.             $exception $exception->getExceptions();
  124.         }
  125.         if (\is_array($exception)) {
  126.             foreach ($exception as $nestedException) {
  127.                 $this->captureException($nestedException$willRetry);
  128.             }
  129.             return;
  130.         }
  131.         $hint EventHint::fromArray([
  132.             'exception' => $exception,
  133.             'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC$willRetry),
  134.         ]);
  135.         $this->hub->captureEvent(Event::createEvent(), $hint);
  136.     }
  137.     private function flushClient(): void
  138.     {
  139.         $client $this->hub->getClient();
  140.         if (null !== $client) {
  141.             $client->flush();
  142.         }
  143.     }
  144. }