Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #21003 [Console][FrameworkBundle] Log console exceptions (jam…
…eshalsall, chalasr) This PR was merged into the 3.3-dev branch. Discussion ---------- [Console][FrameworkBundle] Log console exceptions | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #10895 | License | MIT | Doc PR | symfony/symfony-docs#7373 Continues #19382, fixing some issues including: - ability to display the input string for any `InputInterface` implementation (cast to string if possible, use the command name otherwise) - if the input can be casted as string, cleanup the result (from `command "'command:name' --foo=bar" ` to `command "command:name --foo=bar"`) - made `ExceptionLister::$logger` private instead of protected - changed methods name from `onKernel*` to `onConsole*` (e.g. `onConsoleException`) and removed unnecessary doc blocks - Added more tests Log for an exception: > [2016-12-22 00:34:42] app.ERROR: Exception thrown while running command: "cache:clear -vvv". Message: "An error occured!" {"exception":"[object] (RuntimeException(code: 0): An error occured! at /Volumes/HD/Sites/tests/sf-demo-3.2/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php:61)","command":"cache:clear -vvv","message":"An error occured!"} [] Commits ------- 919041c Add Console ExceptionListener 9896547 Add basic support for automatic console exception logging
- Loading branch information
Showing
6 changed files
with
229 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?xml version="1.0" ?> | ||
|
||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
|
||
<service id="console.exception_listener" class="Symfony\Component\Console\EventListener\ExceptionListener" public="false"> | ||
<argument type="service" id="logger" on-invalid="null" /> | ||
<tag name="kernel.event_subscriber" /> | ||
<tag name="monolog.logger" channel="console" /> | ||
</service> | ||
|
||
</services> | ||
</container> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/Symfony/Component/Console/EventListener/ExceptionListener.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?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\Console\EventListener; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use Symfony\Component\Console\Event\ConsoleEvent; | ||
use Symfony\Component\Console\ConsoleEvents; | ||
use Symfony\Component\Console\Event\ConsoleExceptionEvent; | ||
use Symfony\Component\Console\Event\ConsoleTerminateEvent; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
|
||
/** | ||
* @author James Halsall <james.t.halsall@googlemail.com> | ||
* @author Robin Chalas <robin.chalas@gmail.com> | ||
*/ | ||
class ExceptionListener implements EventSubscriberInterface | ||
{ | ||
private $logger; | ||
|
||
public function __construct(LoggerInterface $logger = null) | ||
{ | ||
$this->logger = $logger; | ||
} | ||
|
||
public function onConsoleException(ConsoleExceptionEvent $event) | ||
{ | ||
if (null === $this->logger) { | ||
return; | ||
} | ||
|
||
$exception = $event->getException(); | ||
|
||
$this->logger->error('Exception thrown while running command "{command}". Message: "{message}"', array('exception' => $exception, 'command' => $this->getInputString($event), 'message' => $exception->getMessage())); | ||
} | ||
|
||
public function onConsoleTerminate(ConsoleTerminateEvent $event) | ||
{ | ||
if (null === $this->logger) { | ||
return; | ||
} | ||
|
||
$exitCode = $event->getExitCode(); | ||
|
||
if (0 === $exitCode) { | ||
return; | ||
} | ||
|
||
$this->logger->error('Command "{command}" exited with code "{code}"', array('command' => $this->getInputString($event), 'code' => $exitCode)); | ||
} | ||
|
||
public static function getSubscribedEvents() | ||
{ | ||
return array( | ||
ConsoleEvents::EXCEPTION => array('onConsoleException', -128), | ||
ConsoleEvents::TERMINATE => array('onConsoleTerminate', -128), | ||
); | ||
} | ||
|
||
private static function getInputString(ConsoleEvent $event) | ||
{ | ||
$commandName = $event->getCommand()->getName(); | ||
$input = $event->getInput(); | ||
|
||
if (method_exists($input, '__toString')) { | ||
return str_replace(array("'$commandName'", "\"$commandName\""), $commandName, (string) $input); | ||
} | ||
|
||
return $commandName; | ||
} | ||
} |
125 changes: 125 additions & 0 deletions
125
src/Symfony/Component/Console/Tests/EventListener/ExceptionListenerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?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\Console\Tests\EventListener; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Event\ConsoleExceptionEvent; | ||
use Symfony\Component\Console\Event\ConsoleTerminateEvent; | ||
use Symfony\Component\Console\EventListener\ExceptionListener; | ||
use Symfony\Component\Console\Input\ArgvInput; | ||
use Symfony\Component\Console\Input\ArrayInput; | ||
use Symfony\Component\Console\Input\StringInput; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
class ExceptionListenerTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
public function testOnConsoleException() | ||
{ | ||
$exception = new \RuntimeException('An error occurred'); | ||
|
||
$logger = $this->getLogger(); | ||
$logger | ||
->expects($this->once()) | ||
->method('error') | ||
->with('Exception thrown while running command "{command}". Message: "{message}"', array('exception' => $exception, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred')) | ||
; | ||
|
||
$listener = new ExceptionListener($logger); | ||
$listener->onConsoleException($this->getConsoleExceptionEvent($exception, new ArgvInput(array('console.php', 'test:run', '--foo=baz', 'buzz')), 1)); | ||
} | ||
|
||
public function testOnConsoleTerminateForNonZeroExitCodeWritesToLog() | ||
{ | ||
$logger = $this->getLogger(); | ||
$logger | ||
->expects($this->once()) | ||
->method('error') | ||
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run', 'code' => 255)) | ||
; | ||
|
||
$listener = new ExceptionListener($logger); | ||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run')), 255)); | ||
} | ||
|
||
public function testOnConsoleTerminateForZeroExitCodeDoesNotWriteToLog() | ||
{ | ||
$logger = $this->getLogger(); | ||
$logger | ||
->expects($this->never()) | ||
->method('error') | ||
; | ||
|
||
$listener = new ExceptionListener($logger); | ||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run')), 0)); | ||
} | ||
|
||
public function testGetSubscribedEvents() | ||
{ | ||
$this->assertEquals( | ||
array( | ||
'console.exception' => array('onConsoleException', -128), | ||
'console.terminate' => array('onConsoleTerminate', -128), | ||
), | ||
ExceptionListener::getSubscribedEvents() | ||
); | ||
} | ||
|
||
public function testAllKindsOfInputCanBeLogged() | ||
{ | ||
$logger = $this->getLogger(); | ||
$logger | ||
->expects($this->exactly(3)) | ||
->method('error') | ||
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run --foo=bar', 'code' => 255)) | ||
; | ||
|
||
$listener = new ExceptionListener($logger); | ||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run', '--foo=bar')), 255)); | ||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArrayInput(array('name' => 'test:run', '--foo' => 'bar')), 255)); | ||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new StringInput('test:run --foo=bar'), 255)); | ||
} | ||
|
||
public function testCommandNameIsDisplayedForNonStringableInput() | ||
{ | ||
$logger = $this->getLogger(); | ||
$logger | ||
->expects($this->once()) | ||
->method('error') | ||
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run', 'code' => 255)) | ||
; | ||
|
||
$listener = new ExceptionListener($logger); | ||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent($this->getMockBuilder(InputInterface::class)->getMock(), 255)); | ||
} | ||
|
||
private function getLogger() | ||
{ | ||
return $this->getMockForAbstractClass(LoggerInterface::class); | ||
} | ||
|
||
private function getConsoleExceptionEvent(\Exception $exception, InputInterface $input, $exitCode) | ||
{ | ||
return new ConsoleExceptionEvent(new Command('test:run'), $input, $this->getOutput(), $exception, $exitCode); | ||
} | ||
|
||
private function getConsoleTerminateEvent(InputInterface $input, $exitCode) | ||
{ | ||
return new ConsoleTerminateEvent(new Command('test:run'), $input, $this->getOutput(), $exitCode); | ||
} | ||
|
||
private function getOutput() | ||
{ | ||
return $this->getMockBuilder(OutputInterface::class)->getMock(); | ||
} | ||
} |