Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 954009a

Browse files
committed
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 ------- cd27b86 [Messenger] Added FailedMessageErrorDetailsStamp
2 parents 14942db + cd27b86 commit 954009a

14 files changed

+417
-77
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

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+
}

src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,33 @@ final class RedeliveryStamp implements StampInterface
2727
public function __construct(int $retryCount, string $exceptionMessage = null, FlattenException $flattenException = null, \DateTimeInterface $redeliveredAt = null)
2828
{
2929
$this->retryCount = $retryCount;
30+
$this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable();
31+
32+
if (null !== $exceptionMessage) {
33+
trigger_deprecation(
34+
'symfony/messenger',
35+
'5.2',
36+
sprintf(
37+
'Using the "$exceptionMessage" parameter in the "%s" class is deprecated, use the "%s" class instead.',
38+
self::class,
39+
ErrorDetailsStamp::class
40+
)
41+
);
42+
}
3043
$this->exceptionMessage = $exceptionMessage;
44+
45+
if (null !== $flattenException) {
46+
trigger_deprecation(
47+
'symfony/messenger',
48+
'5.2',
49+
sprintf(
50+
'Using the "$flattenException" parameter in the "%s" class is deprecated, use the "%s" class instead.',
51+
self::class,
52+
ErrorDetailsStamp::class
53+
)
54+
);
55+
}
3156
$this->flattenException = $flattenException;
32-
$this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable();
3357
}
3458

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

72+
/**
73+
* @deprecated since Symfony 5.2, use ErrorDetailsStamp instead.
74+
*/
4875
public function getExceptionMessage(): ?string
4976
{
77+
trigger_deprecation(
78+
'symfony/messenger',
79+
'5.2',
80+
sprintf(
81+
'Using the "getExceptionMessage()" method of the "%s" class is deprecated, use the "%s" class instead.',
82+
self::class,
83+
ErrorDetailsStamp::class
84+
)
85+
);
86+
5087
return $this->exceptionMessage;
5188
}
5289

90+
/**
91+
* @deprecated since Symfony 5.2, use ErrorDetailsStamp instead.
92+
*/
5393
public function getFlattenException(): ?FlattenException
5494
{
95+
trigger_deprecation(
96+
'symfony/messenger',
97+
'5.2',
98+
sprintf(
99+
'Using the "getFlattenException()" method of the "%s" class is deprecated, use the "%s" class instead.',
100+
self::class,
101+
ErrorDetailsStamp::class
102+
)
103+
);
104+
55105
return $this->flattenException;
56106
}
57107

0 commit comments

Comments
 (0)