Skip to content

Commit

Permalink
[Messenger] Added FailedMessageErrorDetailsStamp
Browse files Browse the repository at this point in the history
  • Loading branch information
TimoBakx committed Feb 9, 2020
1 parent 9bfa258 commit 5770163
Show file tree
Hide file tree
Showing 15 changed files with 412 additions and 75 deletions.
Expand Up @@ -75,6 +75,10 @@ public function testItSendsAndReceivesMessages()
$this->assertEmpty(iterator_to_array($receiver->get()));
}

/**
* @group legacy
* ^ for now, deprecation errors are thrown during serialization.
*/
public function testRetryAndDelay()
{
$serializer = $this->createSerializer();
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
@@ -1,6 +1,8 @@
CHANGELOG
=========

* The `RedeliveryStamp` will no longer be populated with error data. This information is now stored in the `ErrorDetailsStamp` instead.

5.1.0
-----

Expand Down
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
Expand Down Expand Up @@ -61,7 +62,11 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)

/** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope);
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
/** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
$lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);

$rows = [
['Class', \get_class($envelope->getMessage())],
Expand All @@ -71,14 +76,34 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
$rows[] = ['Message Id', $id];
}

$flattenException = null === $lastRedeliveryStampWithException ? null : $lastRedeliveryStampWithException->getFlattenException();
if (null === $sentToFailureTransportStamp) {
$io->warning('Message does not appear to have been sent to this transport after failing');
} else {
$failedAt = '';
$errorMessage = '';
$errorCode = '';
$errorClass = '(unknown)';

if (null !== $lastRedeliveryStamp) {
$failedAt = $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s');
}

if (null !== $lastErrorDetailsStamp) {
$errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
$errorCode = $lastErrorDetailsStamp->getExceptionCode();
$errorClass = $lastErrorDetailsStamp->getExceptionClass();
} elseif (null !== $lastRedeliveryStampWithException) {
$errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
if (null !== $lastRedeliveryStampWithException->getFlattenException()) {
$errorClass = $lastRedeliveryStampWithException->getFlattenException()->getClass();
}
}

$rows = array_merge($rows, [
['Failed at', null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getRedeliveredAt()->format('Y-m-d H:i:s')],
['Error', null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getExceptionMessage()],
['Error Class', null === $flattenException ? '(unknown)' : $flattenException->getClass()],
['Failed at', $failedAt],
['Error', $errorMessage],
['Error Code', $errorCode],
['Error Class', $errorClass],
['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
]);
}
Expand All @@ -98,6 +123,12 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
$dump = new Dumper($io);
$io->writeln($dump($envelope->getMessage()));
$io->title('Exception:');
$flattenException = null;
if (null !== $lastErrorDetailsStamp) {
$flattenException = $lastErrorDetailsStamp->getFlattenException();
} elseif (null !== $lastRedeliveryStampWithException) {
$flattenException = $lastRedeliveryStampWithException->getFlattenException();
}
$io->writeln(null === $flattenException ? '(no data)' : $flattenException->getTraceAsString());
} else {
$io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
Expand All @@ -122,6 +153,23 @@ protected function getReceiver(): ReceiverInterface

protected function getLastRedeliveryStampWithException(Envelope $envelope): ?RedeliveryStamp
{
// Use ErrorDetailsStamp instead if it is available
if ($envelope->last(ErrorDetailsStamp::class)) {
return null;
}

if (null === \func_get_args()[1]) {
trigger_deprecation(
'symfony/messenger',
'5.1',
sprintf(
'Using the "getLastRedeliveryStampWithException" method in the "%s" class is deprecated, use the "Envelope::last(%s)" instead.',
self::class,
ErrorDetailsStamp::class
)
);
}

/** @var RedeliveryStamp $stamp */
foreach (array_reverse($envelope->all(RedeliveryStamp::class)) as $stamp) {
if (null !== $stamp->getExceptionMessage()) {
Expand Down
Expand Up @@ -18,6 +18,8 @@
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;

/**
Expand Down Expand Up @@ -82,13 +84,16 @@ private function listMessages(SymfonyStyle $io, int $max)

$rows = [];
foreach ($envelopes as $envelope) {
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope);
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
/** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
$lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);

$rows[] = [
$this->getMessageId($envelope),
\get_class($envelope->getMessage()),
null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getRedeliveredAt()->format('Y-m-d H:i:s'),
null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getExceptionMessage(),
null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s'),
null === $lastErrorDetailsStamp ? '' : $lastErrorDetailsStamp->getExceptionMessage(),
];
}

Expand Down
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Messenger\Event;

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\StampInterface;

abstract class AbstractWorkerMessageEvent
{
Expand All @@ -36,4 +37,12 @@ public function getReceiverName(): string
{
return $this->receiverName;
}

/**
* Add stamps to the envelope.
*/
public function addStamps(StampInterface ...$stamps): void
{
$this->envelope = $this->envelope->with(...$stamps);
}
}
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;

final class AddErrorDetailsStampListener implements EventSubscriberInterface
{
public function onMessageFailed(WorkerMessageFailedEvent $event): void
{
/** @var ErrorDetailsStamp|null $previousStamp */
$previousStamp = $event->getEnvelope()->last(ErrorDetailsStamp::class);
$stamp = new ErrorDetailsStamp($event->getThrowable());

// Do not append duplicate information
if (null === $previousStamp || !$previousStamp->equals($stamp)) {
$event->addStamps($stamp);
}
}

public static function getSubscribedEvents(): array
{
return [
// must have higher priority than SendFailedMessageForRetryListener
WorkerMessageFailedEvent::class => ['onMessageFailed', 200],
];
}
}
Expand Up @@ -11,10 +11,8 @@
namespace Symfony\Component\Messenger\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
Expand Down Expand Up @@ -49,16 +47,10 @@ public function onMessageFailed(WorkerMessageFailedEvent $event)
return;
}

$throwable = $event->getThrowable();
if ($throwable instanceof HandlerFailedException) {
$throwable = $throwable->getNestedExceptions()[0];
}

$flattenedException = class_exists(FlattenException::class) ? FlattenException::createFromThrowable($throwable) : null;
$envelope = $envelope->with(
new SentToFailureTransportStamp($event->getReceiverName()),
new DelayStamp(0),
new RedeliveryStamp(0, $throwable->getMessage(), $flattenedException)
new RedeliveryStamp(0)
);

if (null !== $this->logger) {
Expand Down
82 changes: 82 additions & 0 deletions src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php
@@ -0,0 +1,82 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Stamp;

use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Throwable;

/**
* Stamp applied when a messages fails due to an exception in the handler.
*/
final class ErrorDetailsStamp implements StampInterface
{
/** @var string */
private $exceptionClass;

/** @var int|mixed */
private $exceptionCode;

/** @var string */
private $exceptionMessage;

/** @var FlattenException|null */
private $flattenException;

public function __construct(Throwable $throwable)
{
if ($throwable instanceof HandlerFailedException) {
$throwable = $throwable->getPrevious();
}

$this->exceptionClass = \get_class($throwable);
$this->exceptionCode = $throwable->getCode();
$this->exceptionMessage = $throwable->getMessage();

if (class_exists(FlattenException::class)) {
$this->flattenException = FlattenException::createFromThrowable($throwable);
}
}

public function getExceptionClass(): string
{
return $this->exceptionClass;
}

public function getExceptionCode()
{
return $this->exceptionCode;
}

public function getExceptionMessage(): string
{
return $this->exceptionMessage;
}

public function getFlattenException(): ?FlattenException
{
return $this->flattenException;
}

public function equals(self $that): bool
{
if ($this->flattenException && $that->flattenException) {
return $this->flattenException->getClass() === $that->flattenException->getClass()
&& $this->flattenException->getCode() === $that->flattenException->getCode()
&& $this->flattenException->getMessage() === $that->flattenException->getMessage();
}

return $this->exceptionClass === $that->exceptionClass
&& $this->exceptionCode === $that->exceptionCode
&& $this->exceptionMessage === $that->exceptionMessage;
}
}
52 changes: 51 additions & 1 deletion src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php
Expand Up @@ -27,9 +27,33 @@ final class RedeliveryStamp implements StampInterface
public function __construct(int $retryCount, string $exceptionMessage = null, FlattenException $flattenException = null)
{
$this->retryCount = $retryCount;
$this->redeliveredAt = new \DateTimeImmutable();

if (null !== $exceptionMessage) {
trigger_deprecation(
'symfony/messenger',
'5.1',
sprintf(
'Using the "$exceptionMessage" parameter in the "%s" class is deprecated, use the "%s" class instead.',
self::class,
ErrorDetailsStamp::class
)
);
}
$this->exceptionMessage = $exceptionMessage;

if (null !== $flattenException) {
trigger_deprecation(
'symfony/messenger',
'5.1',
sprintf(
'Using the "$flattenException" parameter in the "%s" class is deprecated, use the "%s" class instead.',
self::class,
ErrorDetailsStamp::class
)
);
}
$this->flattenException = $flattenException;
$this->redeliveredAt = new \DateTimeImmutable();
}

public static function getRetryCountFromEnvelope(Envelope $envelope): int
Expand All @@ -45,13 +69,39 @@ public function getRetryCount(): int
return $this->retryCount;
}

/**
* @deprecated since Symfony 5.1, use ErrorDetailsStamp instead.
*/
public function getExceptionMessage(): ?string
{
trigger_deprecation(
'symfony/messenger',
'5.1',
sprintf(
'Using the "getExceptionMessage()" method of the "%s" class is deprecated, use the "%s" class instead.',
self::class,
ErrorDetailsStamp::class
)
);

return $this->exceptionMessage;
}

/**
* @deprecated since Symfony 5.1, use ErrorDetailsStamp instead.
*/
public function getFlattenException(): ?FlattenException
{
trigger_deprecation(
'symfony/messenger',
'5.1',
sprintf(
'Using the "getFlattenException()" method of the "%s" class is deprecated, use the "%s" class instead.',
self::class,
ErrorDetailsStamp::class
)
);

return $this->flattenException;
}

Expand Down

0 comments on commit 5770163

Please sign in to comment.