Add Console ExceptionListener
Handle non string-castable inputs Cleanup input for display Naming changes InputInterface doesnt have a toString() Logger must be private Remove useless doc blocks Tweak tests
This commit is contained in:
parent
9896547a4d
commit
919041c1ad
@ -28,6 +28,7 @@ use Symfony\Component\Config\Resource\DirectoryResource;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\Config\Resource\ClassExistenceResource;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\Serializer\Encoder\YamlEncoder;
|
||||
use Symfony\Component\Serializer\Encoder\CsvEncoder;
|
||||
@ -37,6 +38,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
|
||||
use Symfony\Component\Workflow;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/**
|
||||
* FrameworkExtension.
|
||||
@ -81,7 +83,11 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
|
||||
$loader->load('fragment_renderer.xml');
|
||||
$loader->load('console.xml');
|
||||
|
||||
$container->addResource(new ClassExistenceResource(Application::class));
|
||||
if (class_exists(Application::class)) {
|
||||
$loader->load('console.xml');
|
||||
}
|
||||
|
||||
// Property access is used by both the Form and the Validator component
|
||||
$loader->load('property_access.xml');
|
||||
|
@ -5,9 +5,12 @@
|
||||
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">
|
||||
<tag name="kernel.event_subscriber" />
|
||||
|
||||
<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>
|
||||
|
@ -21,7 +21,7 @@
|
||||
"symfony/class-loader": "~3.2",
|
||||
"symfony/dependency-injection": "~3.3",
|
||||
"symfony/config": "~3.3",
|
||||
"symfony/event-dispatcher": "~2.8|~3.0",
|
||||
"symfony/event-dispatcher": "~3.3",
|
||||
"symfony/http-foundation": "~3.1",
|
||||
"symfony/http-kernel": "~3.3",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
|
@ -4,6 +4,7 @@ CHANGELOG
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* added `ExceptionListener`
|
||||
* added `AddConsoleCommandPass` (originally in FrameworkBundle)
|
||||
|
||||
3.2.0
|
||||
|
@ -12,58 +12,37 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Console exception listener.
|
||||
*
|
||||
* Attempts to log exceptions or abnormal terminations of console commands.
|
||||
*
|
||||
* @author James Halsall <james.t.halsall@googlemail.com>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class ExceptionListener implements EventSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger A logger
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles console command exception.
|
||||
*
|
||||
* @param ConsoleExceptionEvent $event Console event
|
||||
*/
|
||||
public function onKernelException(ConsoleExceptionEvent $event)
|
||||
public function onConsoleException(ConsoleExceptionEvent $event)
|
||||
{
|
||||
if (null === $this->logger) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exception = $event->getException();
|
||||
$input = (string) $event->getInput();
|
||||
|
||||
$this->logger->error('Exception thrown while running command: "{command}". Message: "{message}"', array('exception' => $exception, 'command' => $input, 'message' => $exception->getMessage()));
|
||||
$this->logger->error('Exception thrown while running command "{command}". Message: "{message}"', array('exception' => $exception, 'command' => $this->getInputString($event), 'message' => $exception->getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles termination of console command.
|
||||
*
|
||||
* @param ConsoleTerminateEvent $event Console event
|
||||
*/
|
||||
public function onKernelTerminate(ConsoleTerminateEvent $event)
|
||||
public function onConsoleTerminate(ConsoleTerminateEvent $event)
|
||||
{
|
||||
if (null === $this->logger) {
|
||||
return;
|
||||
@ -71,20 +50,30 @@ class ExceptionListener implements EventSubscriberInterface
|
||||
|
||||
$exitCode = $event->getExitCode();
|
||||
|
||||
if ($exitCode === 0) {
|
||||
if (0 === $exitCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$input = (string) $event->getInput();
|
||||
|
||||
$this->logger->error('Command "{command}" exited with status code "{code}"', array('command' => (string) $input, 'code' => $exitCode));
|
||||
$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('onKernelException', -128),
|
||||
ConsoleEvents::TERMINATE => array('onKernelTerminate', -128),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -16,83 +16,110 @@ 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\Tests\Output\TestOutput;
|
||||
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 testOnKernelException()
|
||||
public function testOnConsoleException()
|
||||
{
|
||||
$logger = $this->getLogger();
|
||||
$listener = new ExceptionListener($logger);
|
||||
|
||||
$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'))
|
||||
->with('Exception thrown while running command "{command}". Message: "{message}"', array('exception' => $exception, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred'))
|
||||
;
|
||||
|
||||
$input = array(
|
||||
'name' => 'test:run',
|
||||
'--foo' => 'baz',
|
||||
'bar' => 'buzz'
|
||||
);
|
||||
|
||||
$listener->onKernelException($this->getConsoleExceptionEvent($exception, $input, 1));
|
||||
$listener = new ExceptionListener($logger);
|
||||
$listener->onConsoleException($this->getConsoleExceptionEvent($exception, new ArgvInput(array('console.php', 'test:run', '--foo=baz', 'buzz')), 1));
|
||||
}
|
||||
|
||||
public function testOnKernelTerminateForNonZeroExitCodeWritesToLog()
|
||||
public function testOnConsoleTerminateForNonZeroExitCodeWritesToLog()
|
||||
{
|
||||
$logger = $this->getLogger();
|
||||
$listener = new ExceptionListener($logger);
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('error')
|
||||
->with('Command "{command}" exited with status code "{code}"', array('command' => '\'test:run\'', 'code' => 255))
|
||||
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run', 'code' => 255))
|
||||
;
|
||||
|
||||
$listener->onKernelTerminate($this->getConsoleTerminateEvent(array('name' => 'test:run'), 255));
|
||||
$listener = new ExceptionListener($logger);
|
||||
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run')), 255));
|
||||
}
|
||||
|
||||
public function testOnKernelTerminateForZeroExitCodeDoesNotWriteToLog()
|
||||
public function testOnConsoleTerminateForZeroExitCodeDoesNotWriteToLog()
|
||||
{
|
||||
$logger = $this->getLogger();
|
||||
$listener = new ExceptionListener($logger);
|
||||
|
||||
$logger
|
||||
->expects($this->never())
|
||||
->method('error')
|
||||
;
|
||||
|
||||
$listener->onKernelTerminate($this->getConsoleTerminateEvent(array('name' => 'test:run'), 0));
|
||||
$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('onKernelException', -128),
|
||||
'console.terminate' => array('onKernelTerminate', -128),
|
||||
'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, $input, $exitCode)
|
||||
private function getConsoleExceptionEvent(\Exception $exception, InputInterface $input, $exitCode)
|
||||
{
|
||||
return new ConsoleExceptionEvent(new Command('test:run'), new ArrayInput($input), new TestOutput(), $exception, $exitCode);
|
||||
return new ConsoleExceptionEvent(new Command('test:run'), $input, $this->getOutput(), $exception, $exitCode);
|
||||
}
|
||||
|
||||
private function getConsoleTerminateEvent($input, $exitCode)
|
||||
private function getConsoleTerminateEvent(InputInterface $input, $exitCode)
|
||||
{
|
||||
return new ConsoleTerminateEvent(new Command('test:run'), new ArrayInput($input), new TestOutput(), $exitCode);
|
||||
return new ConsoleTerminateEvent(new Command('test:run'), $input, $this->getOutput(), $exitCode);
|
||||
}
|
||||
|
||||
private function getOutput()
|
||||
{
|
||||
return $this->getMockBuilder(OutputInterface::class)->getMock();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user