feature #38940 [Messenger] Improve formatting of exception in failed message (Jeroen Noten)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Messenger] Improve formatting of exception in failed message

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | not really, enhancement of an existing feature
| Deprecations? | no
| Tickets       | Fix #32310
| License       | MIT

This PR improves the formatting of exception details in failed messenges when displayed using `messenger:failed:show <id> -vv`.

Before:
<img width="807" alt="Screen Shot 2020-11-01 at 1 05 24 PM" src="https://user-images.githubusercontent.com/4370753/97802602-ea593200-1c44-11eb-8bcb-7fcf2d3f1db0.png">

After:
<img width="803" alt="Screen Shot 2020-11-01 at 1 03 09 PM" src="https://user-images.githubusercontent.com/4370753/97802615-f0e7a980-1c44-11eb-8c12-46b2d4510364.png">

I created a `ThrownExceptionDetails` class which will be displayed as a normal exception when dumped with the VarDumper component. Not sure if this is the right way to do it and if the class is in the right namespace, but this is the best solution I could came up with to fix #32310. I'm open for other suggestions.

Commits
-------

2ad1adda69 [Messenger] Improve formatting of thrown exception in show failed message command
This commit is contained in:
Fabien Potencier 2020-11-04 08:21:06 +01:00
commit d2bac32525
2 changed files with 71 additions and 2 deletions

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
@ -21,6 +22,11 @@ use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Caster\TraceStub;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
* @author Ryan Weaver <ryan@symfonycasts.com>
@ -121,7 +127,7 @@ abstract class AbstractFailedMessagesCommand extends Command
if ($io->isVeryVerbose()) {
$io->title('Message:');
$dump = new Dumper($io);
$dump = new Dumper($io, null, $this->createCloner());
$io->writeln($dump($envelope->getMessage()));
$io->title('Exception:');
$flattenException = null;
@ -130,7 +136,7 @@ abstract class AbstractFailedMessagesCommand extends Command
} elseif (null !== $lastRedeliveryStampWithException) {
$flattenException = $lastRedeliveryStampWithException->getFlattenException();
}
$io->writeln(null === $flattenException ? '(no data)' : $flattenException->getTraceAsString());
$io->writeln(null === $flattenException ? '(no data)' : $dump($flattenException));
} else {
$io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
}
@ -172,4 +178,26 @@ abstract class AbstractFailedMessagesCommand extends Command
return null;
}
private function createCloner(): ?ClonerInterface
{
if (!class_exists(VarCloner::class)) {
return null;
}
$cloner = new VarCloner();
$cloner->addCasters([FlattenException::class => function (FlattenException $flattenException, array $a, Stub $stub): array {
$stub->class = $flattenException->getClass();
return [
Caster::PREFIX_VIRTUAL.'message' => $flattenException->getMessage(),
Caster::PREFIX_VIRTUAL.'code' => $flattenException->getCode(),
Caster::PREFIX_VIRTUAL.'file' => $flattenException->getFile(),
Caster::PREFIX_VIRTUAL.'line' => $flattenException->getLine(),
Caster::PREFIX_VIRTUAL.'trace' => new TraceStub($flattenException->getTrace()),
];
}]);
return $cloner;
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Messenger\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
use Symfony\Component\Messenger\Envelope;
@ -234,4 +235,44 @@ EOF
$tester = new CommandTester($command);
$tester->execute(['id' => 15]);
}
public function testVeryVerboseOutputForSingleMessageContainsExceptionWithTrace()
{
$exception = new \RuntimeException('Things are bad!');
$exceptionLine = __LINE__ - 1;
$envelope = new Envelope(new \stdClass(), [
new TransportMessageIdStamp(15),
new SentToFailureTransportStamp('async'),
new RedeliveryStamp(0),
new ErrorDetailsStamp($exception),
]);
$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('find')->with(42)->willReturn($envelope);
$command = new FailedMessagesShowCommand('failure_receiver', $receiver);
$tester = new CommandTester($command);
$tester->execute(['id' => 42], ['verbosity' => OutputInterface::VERBOSITY_VERY_VERBOSE]);
$this->assertStringMatchesFormat(sprintf(<<<'EOF'
%%A
Exception:
==========
RuntimeException {
message: "Things are bad!"
code: 0
file: "%s"
line: %d
trace: {
%%s%%eTests%%eCommand%%eFailedMessagesShowCommandTest.php:%d {
Symfony\Component\Messenger\Tests\Command\FailedMessagesShowCommandTest->testVeryVerboseOutputForSingleMessageContainsExceptionWithTrace()
{
$exception = new \RuntimeException('Things are bad!');
$exceptionLine = __LINE__ - 1;
}
%%A
EOF
,
__FILE__, $exceptionLine, $exceptionLine),
$tester->getDisplay(true));
}
}