AbstractFailedMessagesCommand.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Messenger\Command;
  11. use Symfony\Component\Console\Command\Command;
  12. use Symfony\Component\Console\Helper\Dumper;
  13. use Symfony\Component\Console\Style\SymfonyStyle;
  14. use Symfony\Component\ErrorHandler\Exception\FlattenException;
  15. use Symfony\Component\Messenger\Envelope;
  16. use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
  17. use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
  18. use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
  19. use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
  20. use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
  21. use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
  22. use Symfony\Component\VarDumper\Caster\Caster;
  23. use Symfony\Component\VarDumper\Caster\TraceStub;
  24. use Symfony\Component\VarDumper\Cloner\ClonerInterface;
  25. use Symfony\Component\VarDumper\Cloner\Stub;
  26. use Symfony\Component\VarDumper\Cloner\VarCloner;
  27. /**
  28. * @author Ryan Weaver <ryan@symfonycasts.com>
  29. *
  30. * @internal
  31. */
  32. abstract class AbstractFailedMessagesCommand extends Command
  33. {
  34. private $receiverName;
  35. private $receiver;
  36. public function __construct(string $receiverName, ReceiverInterface $receiver)
  37. {
  38. $this->receiverName = $receiverName;
  39. $this->receiver = $receiver;
  40. parent::__construct();
  41. }
  42. protected function getReceiverName(): string
  43. {
  44. return $this->receiverName;
  45. }
  46. /**
  47. * @return mixed|null
  48. */
  49. protected function getMessageId(Envelope $envelope)
  50. {
  51. /** @var TransportMessageIdStamp $stamp */
  52. $stamp = $envelope->last(TransportMessageIdStamp::class);
  53. return null !== $stamp ? $stamp->getId() : null;
  54. }
  55. protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
  56. {
  57. $io->title('Failed Message Details');
  58. /** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
  59. $sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
  60. /** @var RedeliveryStamp|null $lastRedeliveryStamp */
  61. $lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
  62. /** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
  63. $lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);
  64. $lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);
  65. $rows = [
  66. ['Class', \get_class($envelope->getMessage())],
  67. ];
  68. if (null !== $id = $this->getMessageId($envelope)) {
  69. $rows[] = ['Message Id', $id];
  70. }
  71. if (null === $sentToFailureTransportStamp) {
  72. $io->warning('Message does not appear to have been sent to this transport after failing');
  73. } else {
  74. $failedAt = '';
  75. $errorMessage = '';
  76. $errorCode = '';
  77. $errorClass = '(unknown)';
  78. if (null !== $lastRedeliveryStamp) {
  79. $failedAt = $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s');
  80. }
  81. if (null !== $lastErrorDetailsStamp) {
  82. $errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
  83. $errorCode = $lastErrorDetailsStamp->getExceptionCode();
  84. $errorClass = $lastErrorDetailsStamp->getExceptionClass();
  85. } elseif (null !== $lastRedeliveryStampWithException) {
  86. // Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
  87. $errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
  88. if (null !== $lastRedeliveryStampWithException->getFlattenException()) {
  89. $errorClass = $lastRedeliveryStampWithException->getFlattenException()->getClass();
  90. }
  91. }
  92. $rows = array_merge($rows, [
  93. ['Failed at', $failedAt],
  94. ['Error', $errorMessage],
  95. ['Error Code', $errorCode],
  96. ['Error Class', $errorClass],
  97. ['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
  98. ]);
  99. }
  100. $io->table([], $rows);
  101. /** @var RedeliveryStamp[] $redeliveryStamps */
  102. $redeliveryStamps = $envelope->all(RedeliveryStamp::class);
  103. $io->writeln(' Message history:');
  104. foreach ($redeliveryStamps as $redeliveryStamp) {
  105. $io->writeln(sprintf(' * Message failed at <info>%s</info> and was redelivered', $redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')));
  106. }
  107. $io->newLine();
  108. if ($io->isVeryVerbose()) {
  109. $io->title('Message:');
  110. $dump = new Dumper($io, null, $this->createCloner());
  111. $io->writeln($dump($envelope->getMessage()));
  112. $io->title('Exception:');
  113. $flattenException = null;
  114. if (null !== $lastErrorDetailsStamp) {
  115. $flattenException = $lastErrorDetailsStamp->getFlattenException();
  116. } elseif (null !== $lastRedeliveryStampWithException) {
  117. $flattenException = $lastRedeliveryStampWithException->getFlattenException();
  118. }
  119. $io->writeln(null === $flattenException ? '(no data)' : $dump($flattenException));
  120. } else {
  121. $io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
  122. }
  123. }
  124. protected function printPendingMessagesMessage(ReceiverInterface $receiver, SymfonyStyle $io)
  125. {
  126. if ($receiver instanceof MessageCountAwareInterface) {
  127. if (1 === $receiver->getMessageCount()) {
  128. $io->writeln('There is <comment>1</comment> message pending in the failure transport.');
  129. } else {
  130. $io->writeln(sprintf('There are <comment>%d</comment> messages pending in the failure transport.', $receiver->getMessageCount()));
  131. }
  132. }
  133. }
  134. protected function getReceiver(): ReceiverInterface
  135. {
  136. return $this->receiver;
  137. }
  138. protected function getLastRedeliveryStampWithException(Envelope $envelope): ?RedeliveryStamp
  139. {
  140. if (null === \func_get_args()[1]) {
  141. 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));
  142. }
  143. // Use ErrorDetailsStamp instead if it is available
  144. if (null !== $envelope->last(ErrorDetailsStamp::class)) {
  145. return null;
  146. }
  147. /** @var RedeliveryStamp $stamp */
  148. foreach (array_reverse($envelope->all(RedeliveryStamp::class)) as $stamp) {
  149. if (null !== $stamp->getExceptionMessage()) {
  150. return $stamp;
  151. }
  152. }
  153. return null;
  154. }
  155. private function createCloner(): ?ClonerInterface
  156. {
  157. if (!class_exists(VarCloner::class)) {
  158. return null;
  159. }
  160. $cloner = new VarCloner();
  161. $cloner->addCasters([FlattenException::class => function (FlattenException $flattenException, array $a, Stub $stub): array {
  162. $stub->class = $flattenException->getClass();
  163. return [
  164. Caster::PREFIX_VIRTUAL.'message' => $flattenException->getMessage(),
  165. Caster::PREFIX_VIRTUAL.'code' => $flattenException->getCode(),
  166. Caster::PREFIX_VIRTUAL.'file' => $flattenException->getFile(),
  167. Caster::PREFIX_VIRTUAL.'line' => $flattenException->getLine(),
  168. Caster::PREFIX_VIRTUAL.'trace' => new TraceStub($flattenException->getTrace()),
  169. ];
  170. }]);
  171. return $cloner;
  172. }
  173. }