Skip to content

Commit cd27b86

Browse files
committed
[Messenger] Added FailedMessageErrorDetailsStamp
1 parent 127724d commit cd27b86

16 files changed

+430
-79
lines changed

src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ public function testItSendsAndReceivesMessages()
7676
$this->assertEmpty(iterator_to_array($receiver->get()));
7777
}
7878

79+
/**
80+
* @group legacy
81+
* ^ for now, deprecation errors are thrown during serialization.
82+
*/
7983
public function testRetryAndDelay()
8084
{
8185
$serializer = $this->createSerializer();

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.2.0
55
-----
66

7+
* The `RedeliveryStamp` will no longer be populated with error data. This information is now stored in the `ErrorDetailsStamp` instead.
78
* 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.
89
* Added factory methods to `DelayStamp`.
910
* Removed the exception when dispatching a message with a `DispatchAfterCurrentBusStamp` and not in a context of another dispatch call

src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Console\Helper\Dumper;
1616
use Symfony\Component\Console\Style\SymfonyStyle;
1717
use Symfony\Component\Messenger\Envelope;
18+
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
1819
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
1920
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
2021
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
@@ -61,7 +62,11 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
6162

6263
/** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
6364
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
64-
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope);
65+
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
66+
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
67+
/** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
68+
$lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);
69+
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);
6570

6671
$rows = [
6772
['Class', \get_class($envelope->getMessage())],
@@ -71,14 +76,35 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
7176
$rows[] = ['Message Id', $id];
7277
}
7378

74-
$flattenException = null === $lastRedeliveryStampWithException ? null : $lastRedeliveryStampWithException->getFlattenException();
7579
if (null === $sentToFailureTransportStamp) {
7680
$io->warning('Message does not appear to have been sent to this transport after failing');
7781
} else {
82+
$failedAt = '';
83+
$errorMessage = '';
84+
$errorCode = '';
85+
$errorClass = '(unknown)';
86+
87+
if (null !== $lastRedeliveryStamp) {
88+
$failedAt = $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s');
89+
}
90+
91+
if (null !== $lastErrorDetailsStamp) {
92+
$errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
93+
$errorCode = $lastErrorDetailsStamp->getExceptionCode();
94+
$errorClass = $lastErrorDetailsStamp->getExceptionClass();
95+
} elseif (null !== $lastRedeliveryStampWithException) {
96+
// Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
97+
$errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
98+
if (null !== $lastRedeliveryStampWithException->getFlattenException()) {
99+
$errorClass = $lastRedeliveryStampWithException->getFlattenException()->getClass();
100+
}
101+
}
102+
78103
$rows = array_merge($rows, [
79-
['Failed at', null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getRedeliveredAt()->format('Y-m-d H:i:s')],
80-
['Error', null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getExceptionMessage()],
81-
['Error Class', null === $flattenException ? '(unknown)' : $flattenException->getClass()],
104+
['Failed at', $failedAt],
105+
['Error', $errorMessage],
106+
['Error Code', $errorCode],
107+
['Error Class', $errorClass],
82108
['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
83109
]);
84110
}
@@ -98,6 +124,12 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
98124
$dump = new Dumper($io);
99125
$io->writeln($dump($envelope->getMessage()));
100126
$io->title('Exception:');
127+
$flattenException = null;
128+
if (null !== $lastErrorDetailsStamp) {
129+
$flattenException = $lastErrorDetailsStamp->getFlattenException();
130+
} elseif (null !== $lastRedeliveryStampWithException) {
131+
$flattenException = $lastRedeliveryStampWithException->getFlattenException();
132+
}
101133
$io->writeln(null === $flattenException ? '(no data)' : $flattenException->getTraceAsString());
102134
} else {
103135
$io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
@@ -122,6 +154,23 @@ protected function getReceiver(): ReceiverInterface
122154

123155
protected function getLastRedeliveryStampWithException(Envelope $envelope): ?RedeliveryStamp
124156
{
157+
if (null === \func_get_args()[1]) {
158+
trigger_deprecation(
159+
'symfony/messenger',
160+
'5.2',
161+
sprintf(
162+
'Using the "getLastRedeliveryStampWithException" method in the "%s" class is deprecated, use the "Envelope::last(%s)" instead.',
163+
self::class,
164+
ErrorDetailsStamp::class
165+
)
166+
);
167+
}
168+
169+
// Use ErrorDetailsStamp instead if it is available
170+
if (null !== $envelope->last(ErrorDetailsStamp::class)) {
171+
return null;
172+
}
173+
125174
/** @var RedeliveryStamp $stamp */
126175
foreach (array_reverse($envelope->all(RedeliveryStamp::class)) as $stamp) {
127176
if (null !== $stamp->getExceptionMessage()) {

src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1919
use Symfony\Component\Console\Output\OutputInterface;
2020
use Symfony\Component\Console\Style\SymfonyStyle;
21+
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
22+
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
2123
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
2224

2325
/**
@@ -82,13 +84,25 @@ private function listMessages(SymfonyStyle $io, int $max)
8284

8385
$rows = [];
8486
foreach ($envelopes as $envelope) {
85-
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope);
87+
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
88+
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
89+
/** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
90+
$lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);
91+
$lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);
92+
93+
$errorMessage = '';
94+
if (null !== $lastErrorDetailsStamp) {
95+
$errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
96+
} elseif (null !== $lastRedeliveryStampWithException) {
97+
// Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
98+
$errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
99+
}
86100

87101
$rows[] = [
88102
$this->getMessageId($envelope),
89103
\get_class($envelope->getMessage()),
90-
null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getRedeliveredAt()->format('Y-m-d H:i:s'),
91-
null === $lastRedeliveryStampWithException ? '' : $lastRedeliveryStampWithException->getExceptionMessage(),
104+
null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s'),
105+
$errorMessage,
92106
];
93107
}
94108

src/Symfony/Component/Messenger/Event/AbstractWorkerMessageEvent.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Messenger\Event;
1313

1414
use Symfony\Component\Messenger\Envelope;
15+
use Symfony\Component\Messenger\Stamp\StampInterface;
1516

1617
abstract class AbstractWorkerMessageEvent
1718
{
@@ -36,4 +37,9 @@ public function getReceiverName(): string
3637
{
3738
return $this->receiverName;
3839
}
40+
41+
public function addStamps(StampInterface ...$stamps): void
42+
{
43+
$this->envelope = $this->envelope->with(...$stamps);
44+
}
3945
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
16+
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
17+
18+
final class AddErrorDetailsStampListener implements EventSubscriberInterface
19+
{
20+
public function onMessageFailed(WorkerMessageFailedEvent $event): void
21+
{
22+
$stamp = new ErrorDetailsStamp($event->getThrowable());
23+
$previousStamp = $event->getEnvelope()->last(ErrorDetailsStamp::class);
24+
25+
// Do not append duplicate information
26+
if (null === $previousStamp || !$previousStamp->equals($stamp)) {
27+
$event->addStamps($stamp);
28+
}
29+
}
30+
31+
public static function getSubscribedEvents(): array
32+
{
33+
return [
34+
// must have higher priority than SendFailedMessageForRetryListener
35+
WorkerMessageFailedEvent::class => ['onMessageFailed', 200],
36+
];
37+
}
38+
}

src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111
namespace Symfony\Component\Messenger\EventListener;
1212

1313
use Psr\Log\LoggerInterface;
14-
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1514
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1615
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
17-
use Symfony\Component\Messenger\Exception\HandlerFailedException;
1816
use Symfony\Component\Messenger\Stamp\DelayStamp;
1917
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
2018
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
@@ -49,16 +47,10 @@ public function onMessageFailed(WorkerMessageFailedEvent $event)
4947
return;
5048
}
5149

52-
$throwable = $event->getThrowable();
53-
if ($throwable instanceof HandlerFailedException) {
54-
$throwable = $throwable->getNestedExceptions()[0];
55-
}
56-
57-
$flattenedException = class_exists(FlattenException::class) ? FlattenException::createFromThrowable($throwable) : null;
5850
$envelope = $envelope->with(
5951
new SentToFailureTransportStamp($event->getReceiverName()),
6052
new DelayStamp(0),
61-
new RedeliveryStamp(0, $throwable->getMessage(), $flattenedException)
53+
new RedeliveryStamp(0)
6254
);
6355

6456
if (null !== $this->logger) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Stamp;
13+
14+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
15+
use Symfony\Component\Messenger\Exception\HandlerFailedException;
16+
use Throwable;
17+
18+
/**
19+
* Stamp applied when a messages fails due to an exception in the handler.
20+
*/
21+
final class ErrorDetailsStamp implements StampInterface
22+
{
23+
/** @var string */
24+
private $exceptionClass;
25+
26+
/** @var int|mixed */
27+
private $exceptionCode;
28+
29+
/** @var string */
30+
private $exceptionMessage;
31+
32+
/** @var FlattenException|null */
33+
private $flattenException;
34+
35+
public function __construct(Throwable $throwable)
36+
{
37+
if ($throwable instanceof HandlerFailedException) {
38+
$throwable = $throwable->getPrevious();
39+
}
40+
41+
$this->exceptionClass = \get_class($throwable);
42+
$this->exceptionCode = $throwable->getCode();
43+
$this->exceptionMessage = $throwable->getMessage();
44+
45+
if (class_exists(FlattenException::class)) {
46+
$this->flattenException = FlattenException::createFromThrowable($throwable);
47+
}
48+
}
49+
50+
public function getExceptionClass(): string
51+
{
52+
return $this->exceptionClass;
53+
}
54+
55+
public function getExceptionCode()
56+
{
57+
return $this->exceptionCode;
58+
}
59+
60+
public function getExceptionMessage(): string
61+
{
62+
return $this->exceptionMessage;
63+
}
64+
65+
public function getFlattenException(): ?FlattenException
66+
{
67+
return $this->flattenException;
68+
}
69+
70+
public function equals(?self $that): bool
71+
{
72+
if (null === $that) {
73+
return false;
74+
}
75+
76+
if ($this->flattenException && $that->flattenException) {
77+
return $this->flattenException->getClass() === $that->flattenException->getClass()
78+
&& $this->flattenException->getCode() === $that->flattenException->getCode()
79+
&& $this->flattenException->getMessage() === $that->flattenException->getMessage();
80+
}
81+
82+
return $this->exceptionClass === $that->exceptionClass
83+
&& $this->exceptionCode === $that->exceptionCode
84+
&& $this->exceptionMessage === $that->exceptionMessage;
85+
}
86+
}

0 commit comments

Comments
 (0)