feature #32904 [Messenger] Added ErrorDetailsStamp (TimoBakx)
This PR was merged into the 5.2-dev branch.
Discussion
----------
[Messenger] Added ErrorDetailsStamp
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | yes
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #32311
| License | MIT
| Doc PR | No doc changes are needed
#SymfonyHackday
This PR is part of the work started in #32341. That PR has a workaround for showing exceptions added to a previous retry. This PR stores error messages in a separate stamp, so they're more easily accessed.
I also added the exceptionClass as a separate string (independant of FlattenException), so that information is always available, even if the trace is not (due to FlattenException not being available).
Duplicated exceptions (compared to the last one) are not stored separately.
**Questions:**
- Should we limit the total amount of exceptions (remove the oldest when adding a new one)?
- Yes, but in a new PR
- The current implementation adds this stamp in the Worker instead of the listeners to prevent duplicate code (due to the immutability of the envelope in the event). Is this the proper way to do this?
- No, create a new listener and a way to add stamps to the envelope inside the event.
- When adding details of a `HandlerFailedException`, should we add a stamp per wrapped `Throwable`? There can be multiple errors wrapped by a single `HandlerFailedException`.
- Yes, but in a later PR
**Todo:**
- [x] only add new information if it differs from the previous exception
- [x] add deprecations
- [x] fall back to old stamp data if no new stamp is available
- [x] rebase and retarget to master
- [x] update CHANGELOG.md
- [x] check for docs PR
**BC Breaks:**
When this PR is merged, RedeliveryStamps will no longer be populated with exception data. Any implementations that use `RedeliveryStamp::getExceptionMessage()` or `RedeliveryStamp::getFlattenedException()` will receive an empty string or `null` respectively for stamps added after this update. They should rely on `ErrorDetailsStamp` instead.
**New stuff:**
- New stamp `ErrorDetailsStamp`.
- New event subscriber `AddErrorDetailsStampListener`.
- New method `AbstractWorkerMessageEvent::addStamps`.
Commits
-------
cd27b863f9
[Messenger] Added FailedMessageErrorDetailsStamp
This commit is contained in:
commit
954009a4d7
@ -76,6 +76,10 @@ class AmqpExtIntegrationTest extends TestCase
|
||||
$this->assertEmpty(iterator_to_array($receiver->get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* ^ for now, deprecation errors are thrown during serialization.
|
||||
*/
|
||||
public function testRetryAndDelay()
|
||||
{
|
||||
$serializer = $this->createSerializer();
|
||||
|
@ -4,6 +4,7 @@ CHANGELOG
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* The `RedeliveryStamp` will no longer be populated with error data. This information is now stored in the `ErrorDetailsStamp` instead.
|
||||
* Added `FlattenExceptionNormalizer` to give more information about the exception on Messenger background processes. The `FlattenExceptionNormalizer` has a higher priority than `ProblemNormalizer` and it is only used when the Messenger serialization context is set.
|
||||
* Added factory methods to `DelayStamp`.
|
||||
* Removed the exception when dispatching a message with a `DispatchAfterCurrentBusStamp` and not in a context of another dispatch call
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\Console\Command\Command;
|
||||
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;
|
||||
@ -61,7 +62,11 @@ abstract class AbstractFailedMessagesCommand extends Command
|
||||
|
||||
/** @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())],
|
||||
@ -71,14 +76,35 @@ abstract class AbstractFailedMessagesCommand extends Command
|
||||
$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) {
|
||||
// Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
|
||||
$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()],
|
||||
]);
|
||||
}
|
||||
@ -98,6 +124,12 @@ abstract class AbstractFailedMessagesCommand extends Command
|
||||
$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.');
|
||||
@ -122,6 +154,23 @@ abstract class AbstractFailedMessagesCommand extends Command
|
||||
|
||||
protected function getLastRedeliveryStampWithException(Envelope $envelope): ?RedeliveryStamp
|
||||
{
|
||||
if (null === \func_get_args()[1]) {
|
||||
trigger_deprecation(
|
||||
'symfony/messenger',
|
||||
'5.2',
|
||||
sprintf(
|
||||
'Using the "getLastRedeliveryStampWithException" method in the "%s" class is deprecated, use the "Envelope::last(%s)" instead.',
|
||||
self::class,
|
||||
ErrorDetailsStamp::class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Use ErrorDetailsStamp instead if it is available
|
||||
if (null !== $envelope->last(ErrorDetailsStamp::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var RedeliveryStamp $stamp */
|
||||
foreach (array_reverse($envelope->all(RedeliveryStamp::class)) as $stamp) {
|
||||
if (null !== $stamp->getExceptionMessage()) {
|
||||
|
@ -18,6 +18,8 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -82,13 +84,25 @@ EOF
|
||||
|
||||
$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);
|
||||
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);
|
||||
|
||||
$errorMessage = '';
|
||||
if (null !== $lastErrorDetailsStamp) {
|
||||
$errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
|
||||
} elseif (null !== $lastRedeliveryStampWithException) {
|
||||
// Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
|
||||
$errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
|
||||
}
|
||||
|
||||
$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'),
|
||||
$errorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?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
|
||||
{
|
||||
$stamp = new ErrorDetailsStamp($event->getThrowable());
|
||||
$previousStamp = $event->getEnvelope()->last(ErrorDetailsStamp::class);
|
||||
|
||||
// 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],
|
||||
];
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -49,16 +47,10 @@ class SendFailedMessageToFailureTransportListener implements EventSubscriberInte
|
||||
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) {
|
||||
|
86
src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php
Normal file
86
src/Symfony/Component/Messenger/Stamp/ErrorDetailsStamp.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?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 (null === $that) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -27,9 +27,33 @@ final class RedeliveryStamp implements StampInterface
|
||||
public function __construct(int $retryCount, string $exceptionMessage = null, FlattenException $flattenException = null, \DateTimeInterface $redeliveredAt = null)
|
||||
{
|
||||
$this->retryCount = $retryCount;
|
||||
$this->exceptionMessage = $exceptionMessage;
|
||||
$this->flattenException = $flattenException;
|
||||
$this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable();
|
||||
|
||||
if (null !== $exceptionMessage) {
|
||||
trigger_deprecation(
|
||||
'symfony/messenger',
|
||||
'5.2',
|
||||
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.2',
|
||||
sprintf(
|
||||
'Using the "$flattenException" parameter in the "%s" class is deprecated, use the "%s" class instead.',
|
||||
self::class,
|
||||
ErrorDetailsStamp::class
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->flattenException = $flattenException;
|
||||
}
|
||||
|
||||
public static function getRetryCountFromEnvelope(Envelope $envelope): int
|
||||
@ -45,13 +69,39 @@ final class RedeliveryStamp implements StampInterface
|
||||
return $this->retryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 5.2, use ErrorDetailsStamp instead.
|
||||
*/
|
||||
public function getExceptionMessage(): ?string
|
||||
{
|
||||
trigger_deprecation(
|
||||
'symfony/messenger',
|
||||
'5.2',
|
||||
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.2, use ErrorDetailsStamp instead.
|
||||
*/
|
||||
public function getFlattenException(): ?FlattenException
|
||||
{
|
||||
trigger_deprecation(
|
||||
'symfony/messenger',
|
||||
'5.2',
|
||||
sprintf(
|
||||
'Using the "getFlattenException()" method of the "%s" class is deprecated, use the "%s" class instead.',
|
||||
self::class,
|
||||
ErrorDetailsStamp::class
|
||||
)
|
||||
);
|
||||
|
||||
return $this->flattenException;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
|
||||
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;
|
||||
@ -29,11 +30,13 @@ class FailedMessagesShowCommandTest extends TestCase
|
||||
public function testBasicRun()
|
||||
{
|
||||
$sentToFailureStamp = new SentToFailureTransportStamp('async');
|
||||
$redeliveryStamp = new RedeliveryStamp(0, 'Things are bad!');
|
||||
$redeliveryStamp = new RedeliveryStamp(0);
|
||||
$errorStamp = new ErrorDetailsStamp(new \Exception('Things are bad!', 123));
|
||||
$envelope = new Envelope(new \stdClass(), [
|
||||
new TransportMessageIdStamp(15),
|
||||
$sentToFailureStamp,
|
||||
$redeliveryStamp,
|
||||
$errorStamp,
|
||||
]);
|
||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||
$receiver->expects($this->once())->method('find')->with(15)->willReturn($envelope);
|
||||
@ -52,8 +55,9 @@ class FailedMessagesShowCommandTest extends TestCase
|
||||
Message Id 15
|
||||
Failed at %s
|
||||
Error Things are bad!
|
||||
Error Class (unknown)
|
||||
Transport async
|
||||
Error Code 123
|
||||
Error Class Exception
|
||||
Transport async
|
||||
EOF
|
||||
,
|
||||
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||
@ -63,39 +67,74 @@ EOF
|
||||
public function testMultipleRedeliveryFails()
|
||||
{
|
||||
$sentToFailureStamp = new SentToFailureTransportStamp('async');
|
||||
$redeliveryStamp1 = new RedeliveryStamp(0, 'Things are bad!');
|
||||
$redeliveryStamp1 = new RedeliveryStamp(0);
|
||||
$errorStamp = new ErrorDetailsStamp(new \Exception('Things are bad!', 123));
|
||||
$redeliveryStamp2 = new RedeliveryStamp(0);
|
||||
$envelope = new Envelope(new \stdClass(), [
|
||||
new TransportMessageIdStamp(15),
|
||||
$sentToFailureStamp,
|
||||
$redeliveryStamp1,
|
||||
$errorStamp,
|
||||
$redeliveryStamp2,
|
||||
]);
|
||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||
$receiver->expects($this->once())->method('find')->with(15)->willReturn($envelope);
|
||||
|
||||
$command = new FailedMessagesShowCommand(
|
||||
'failure_receiver',
|
||||
$receiver
|
||||
);
|
||||
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute(['id' => 15]);
|
||||
|
||||
$this->assertStringContainsString(sprintf(<<<EOF
|
||||
------------- ---------------------
|
||||
Class stdClass
|
||||
Message Id 15
|
||||
Failed at %s
|
||||
Error Things are bad!
|
||||
Error Class (unknown)
|
||||
Transport async
|
||||
Error Code 123
|
||||
Error Class Exception
|
||||
Transport async
|
||||
EOF
|
||||
,
|
||||
$redeliveryStamp2->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||
$tester->getDisplay(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testLegacyFallback()
|
||||
{
|
||||
$sentToFailureStamp = new SentToFailureTransportStamp('async');
|
||||
$redeliveryStamp = new RedeliveryStamp(0, 'Things are bad!');
|
||||
$envelope = new Envelope(new \stdClass(), [
|
||||
new TransportMessageIdStamp(15),
|
||||
$sentToFailureStamp,
|
||||
$redeliveryStamp,
|
||||
]);
|
||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||
$receiver->expects($this->once())->method('find')->with(15)->willReturn($envelope);
|
||||
$command = new FailedMessagesShowCommand(
|
||||
'failure_receiver',
|
||||
$receiver
|
||||
);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute(['id' => 15]);
|
||||
$this->assertStringContainsString(sprintf(<<<EOF
|
||||
------------- ---------------------
|
||||
Class stdClass
|
||||
Message Id 15
|
||||
Failed at %s
|
||||
Error Things are bad!
|
||||
Error Code
|
||||
Error Class (unknown)
|
||||
Transport async
|
||||
EOF
|
||||
,
|
||||
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||
$tester->getDisplay(true));
|
||||
}
|
||||
|
||||
public function testReceiverShouldBeListable()
|
||||
{
|
||||
$receiver = $this->createMock(ReceiverInterface::class);
|
||||
@ -113,11 +152,13 @@ EOF
|
||||
public function testListMessages()
|
||||
{
|
||||
$sentToFailureStamp = new SentToFailureTransportStamp('async');
|
||||
$redeliveryStamp = new RedeliveryStamp(0, 'Things are bad!');
|
||||
$redeliveryStamp = new RedeliveryStamp(0);
|
||||
$errorStamp = new ErrorDetailsStamp(new \RuntimeException('Things are bad!'));
|
||||
$envelope = new Envelope(new \stdClass(), [
|
||||
new TransportMessageIdStamp(15),
|
||||
$sentToFailureStamp,
|
||||
$redeliveryStamp,
|
||||
$errorStamp,
|
||||
]);
|
||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||
$receiver->expects($this->once())->method('all')->with()->willReturn([$envelope]);
|
||||
@ -130,7 +171,7 @@ EOF
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
$this->assertStringContainsString(sprintf(<<<EOF
|
||||
15 stdClass %s Things are bad!
|
||||
15 stdClass %s Things are bad!
|
||||
EOF
|
||||
,
|
||||
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||
@ -158,7 +199,8 @@ EOF
|
||||
$envelope = new Envelope(new \stdClass(), [
|
||||
new TransportMessageIdStamp(15),
|
||||
$sentToFailureStamp,
|
||||
new RedeliveryStamp(0, 'Things are bad!'),
|
||||
new RedeliveryStamp(0),
|
||||
new ErrorDetailsStamp(new \RuntimeException('Things are bad!')),
|
||||
]);
|
||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||
$receiver->expects($this->once())->method('all')->with()->willReturn([$envelope]);
|
||||
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Messenger\Tests\EventListener;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
use Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener;
|
||||
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
|
||||
|
||||
final class AddErrorDetailsStampListenerTest extends TestCase
|
||||
{
|
||||
public function testExceptionDetailsAreAdded(): void
|
||||
{
|
||||
$listener = new AddErrorDetailsStampListener();
|
||||
|
||||
$envelope = new Envelope(new \stdClass());
|
||||
$exception = new \Exception('It failed!');
|
||||
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
|
||||
$expectedStamp = new ErrorDetailsStamp($exception);
|
||||
|
||||
$listener->onMessageFailed($event);
|
||||
|
||||
$this->assertEquals($expectedStamp, $event->getEnvelope()->last(ErrorDetailsStamp::class));
|
||||
}
|
||||
|
||||
public function testWorkerAddsNewErrorDetailsStampOnFailure()
|
||||
{
|
||||
$listener = new AddErrorDetailsStampListener();
|
||||
|
||||
$envelope = new Envelope(new \stdClass(), [
|
||||
new ErrorDetailsStamp(new \InvalidArgumentException('First error!')),
|
||||
]);
|
||||
|
||||
$exception = new \Exception('Second error!');
|
||||
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
|
||||
$expectedStamp = new ErrorDetailsStamp($exception);
|
||||
|
||||
$listener->onMessageFailed($event);
|
||||
|
||||
$this->assertEquals($expectedStamp, $event->getEnvelope()->last(ErrorDetailsStamp::class));
|
||||
$this->assertCount(2, $event->getEnvelope()->all(ErrorDetailsStamp::class));
|
||||
}
|
||||
|
||||
public function testWorkerDoesNotAddDuplicateErrorDetailsStampOnFailure()
|
||||
{
|
||||
$listener = new AddErrorDetailsStampListener();
|
||||
|
||||
$envelope = new Envelope(new \stdClass(), [new \Exception('It failed!')]);
|
||||
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', new \Exception('It failed!'));
|
||||
|
||||
$listener->onMessageFailed($event);
|
||||
|
||||
$this->assertCount(1, $event->getEnvelope()->all(ErrorDetailsStamp::class));
|
||||
}
|
||||
}
|
@ -15,8 +15,6 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener;
|
||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
|
||||
|
||||
@ -34,11 +32,6 @@ class SendFailedMessageToFailureTransportListenerTest extends TestCase
|
||||
$this->assertNotNull($sentToFailureTransportStamp);
|
||||
$this->assertSame('my_receiver', $sentToFailureTransportStamp->getOriginalReceiverName());
|
||||
|
||||
/** @var RedeliveryStamp $redeliveryStamp */
|
||||
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||
$this->assertSame('no!', $redeliveryStamp->getExceptionMessage());
|
||||
$this->assertSame('no!', $redeliveryStamp->getFlattenException()->getMessage());
|
||||
|
||||
return true;
|
||||
}))->willReturnArgument(0);
|
||||
$listener = new SendFailedMessageToFailureTransportListener($sender);
|
||||
@ -50,30 +43,6 @@ class SendFailedMessageToFailureTransportListenerTest extends TestCase
|
||||
$listener->onMessageFailed($event);
|
||||
}
|
||||
|
||||
public function testItGetsNestedHandlerFailedException()
|
||||
{
|
||||
$sender = $this->createMock(SenderInterface::class);
|
||||
$sender->expects($this->once())->method('send')->with($this->callback(function ($envelope) {
|
||||
/** @var Envelope $envelope */
|
||||
/** @var RedeliveryStamp $redeliveryStamp */
|
||||
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||
$this->assertNotNull($redeliveryStamp);
|
||||
$this->assertSame('I am inside!', $redeliveryStamp->getExceptionMessage());
|
||||
$this->assertSame('Exception', $redeliveryStamp->getFlattenException()->getClass());
|
||||
|
||||
return true;
|
||||
}))->willReturnArgument(0);
|
||||
|
||||
$listener = new SendFailedMessageToFailureTransportListener($sender);
|
||||
|
||||
$envelope = new Envelope(new \stdClass());
|
||||
$exception = new \Exception('I am inside!');
|
||||
$exception = new HandlerFailedException($envelope, [$exception]);
|
||||
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
|
||||
|
||||
$listener->onMessageFailed($event);
|
||||
}
|
||||
|
||||
public function testDoNothingOnRetry()
|
||||
{
|
||||
$sender = $this->createMock(SenderInterface::class);
|
||||
|
@ -16,6 +16,7 @@ use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
use Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener;
|
||||
use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener;
|
||||
use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener;
|
||||
use Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener;
|
||||
@ -27,7 +28,7 @@ use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware;
|
||||
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
|
||||
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
|
||||
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
|
||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
|
||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
|
||||
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
|
||||
@ -37,7 +38,7 @@ use Symfony\Component\Messenger\Worker;
|
||||
|
||||
class FailureIntegrationTest extends TestCase
|
||||
{
|
||||
public function testRequeMechanism()
|
||||
public function testRequeueMechanism()
|
||||
{
|
||||
$transport1 = new DummyFailureTestSenderAndReceiver();
|
||||
$transport2 = new DummyFailureTestSenderAndReceiver();
|
||||
@ -96,6 +97,7 @@ class FailureIntegrationTest extends TestCase
|
||||
new SendMessageMiddleware($senderLocator),
|
||||
new HandleMessageMiddleware($handlerLocator),
|
||||
]);
|
||||
$dispatcher->addSubscriber(new AddErrorDetailsStampListener());
|
||||
$dispatcher->addSubscriber(new SendFailedMessageForRetryListener($locator, $retryStrategyLocator));
|
||||
$dispatcher->addSubscriber(new SendFailedMessageToFailureTransportListener($failureTransport));
|
||||
$dispatcher->addSubscriber(new StopWorkerOnMessageLimitListener(1));
|
||||
@ -156,10 +158,10 @@ class FailureIntegrationTest extends TestCase
|
||||
/** @var SentToFailureTransportStamp $sentToFailureStamp */
|
||||
$sentToFailureStamp = $failedEnvelope->last(SentToFailureTransportStamp::class);
|
||||
$this->assertNotNull($sentToFailureStamp);
|
||||
/** @var RedeliveryStamp $redeliveryStamp */
|
||||
$redeliveryStamp = $failedEnvelope->last(RedeliveryStamp::class);
|
||||
$this->assertNotNull($redeliveryStamp);
|
||||
$this->assertSame('Failure from call 2', $redeliveryStamp->getExceptionMessage());
|
||||
/** @var ErrorDetailsStamp $errorDetailsStamp */
|
||||
$errorDetailsStamp = $failedEnvelope->last(ErrorDetailsStamp::class);
|
||||
$this->assertNotNull($errorDetailsStamp);
|
||||
$this->assertSame('Failure from call 2', $errorDetailsStamp->getExceptionMessage());
|
||||
|
||||
/*
|
||||
* Failed message is handled, fails, and sent for a retry
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?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\Tests\Stamp;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
|
||||
|
||||
class ErrorDetailsStampTest extends TestCase
|
||||
{
|
||||
public function testGetters(): void
|
||||
{
|
||||
$exception = new \Exception('exception message');
|
||||
$flattenException = FlattenException::createFromThrowable($exception);
|
||||
|
||||
$stamp = new ErrorDetailsStamp($exception);
|
||||
|
||||
$this->assertSame(\Exception::class, $stamp->getExceptionClass());
|
||||
$this->assertSame('exception message', $stamp->getExceptionMessage());
|
||||
$this->assertEquals($flattenException, $stamp->getFlattenException());
|
||||
}
|
||||
|
||||
public function testUnwrappingHandlerFailedException(): void
|
||||
{
|
||||
$wrappedException = new \Exception('I am inside', 123);
|
||||
$envelope = new Envelope(new \stdClass());
|
||||
$exception = new HandlerFailedException($envelope, [$wrappedException]);
|
||||
$flattenException = FlattenException::createFromThrowable($wrappedException);
|
||||
|
||||
$stamp = new ErrorDetailsStamp($exception);
|
||||
|
||||
$this->assertSame(\Exception::class, $stamp->getExceptionClass());
|
||||
$this->assertSame('I am inside', $stamp->getExceptionMessage());
|
||||
$this->assertSame(123, $stamp->getExceptionCode());
|
||||
$this->assertEquals($flattenException, $stamp->getFlattenException());
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@
|
||||
namespace Symfony\Component\Messenger\Tests\Stamp;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||
|
||||
class RedeliveryStampTest extends TestCase
|
||||
@ -22,16 +21,6 @@ class RedeliveryStampTest extends TestCase
|
||||
$stamp = new RedeliveryStamp(10);
|
||||
$this->assertSame(10, $stamp->getRetryCount());
|
||||
$this->assertInstanceOf(\DateTimeInterface::class, $stamp->getRedeliveredAt());
|
||||
$this->assertNull($stamp->getExceptionMessage());
|
||||
$this->assertNull($stamp->getFlattenException());
|
||||
}
|
||||
|
||||
public function testGettersPopulated()
|
||||
{
|
||||
$flattenException = new FlattenException();
|
||||
$stamp = new RedeliveryStamp(10, 'exception message', $flattenException);
|
||||
$this->assertSame('exception message', $stamp->getExceptionMessage());
|
||||
$this->assertSame($flattenException, $stamp->getFlattenException());
|
||||
}
|
||||
|
||||
public function testSerialization()
|
||||
|
Reference in New Issue
Block a user