Add basic support for automatic console exception logging

This commit is contained in:
James Halsall 2016-07-18 19:07:57 +01:00 committed by Robin Chalas
parent 5921530a1a
commit 9896547a4d
No known key found for this signature in database
GPG Key ID: 89672113756EE03B
4 changed files with 202 additions and 0 deletions

View File

@ -81,6 +81,7 @@ class FrameworkExtension extends Extension
}
$loader->load('fragment_renderer.xml');
$loader->load('console.xml');
// Property access is used by both the Form and the Validator component
$loader->load('property_access.xml');

View File

@ -0,0 +1,13 @@
<?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">
<tag name="kernel.event_subscriber" />
<argument type="service" id="logger" on-invalid="null" />
</service>
</services>
</container>

View File

@ -0,0 +1,90 @@
<?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\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>
*/
class ExceptionListener implements EventSubscriberInterface
{
/**
* @var LoggerInterface
*/
protected $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)
{
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()));
}
/**
* Handles termination of console command.
*
* @param ConsoleTerminateEvent $event Console event
*/
public function onKernelTerminate(ConsoleTerminateEvent $event)
{
if (null === $this->logger) {
return;
}
$exitCode = $event->getExitCode();
if ($exitCode === 0) {
return;
}
$input = (string) $event->getInput();
$this->logger->error('Command "{command}" exited with status code "{code}"', array('command' => (string) $input, 'code' => $exitCode));
}
public static function getSubscribedEvents()
{
return array(
ConsoleEvents::EXCEPTION => array('onKernelException', -128),
ConsoleEvents::TERMINATE => array('onKernelTerminate', -128),
);
}
}

View File

@ -0,0 +1,98 @@
<?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\ArrayInput;
use Symfony\Component\Console\Tests\Output\TestOutput;
class ExceptionListenerTest extends \PHPUnit_Framework_TestCase
{
public function testOnKernelException()
{
$logger = $this->getLogger();
$listener = new ExceptionListener($logger);
$exception = new \RuntimeException('An error occurred');
$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'))
;
$input = array(
'name' => 'test:run',
'--foo' => 'baz',
'bar' => 'buzz'
);
$listener->onKernelException($this->getConsoleExceptionEvent($exception, $input, 1));
}
public function testOnKernelTerminateForNonZeroExitCodeWritesToLog()
{
$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))
;
$listener->onKernelTerminate($this->getConsoleTerminateEvent(array('name' => 'test:run'), 255));
}
public function testOnKernelTerminateForZeroExitCodeDoesNotWriteToLog()
{
$logger = $this->getLogger();
$listener = new ExceptionListener($logger);
$logger
->expects($this->never())
->method('error')
;
$listener->onKernelTerminate($this->getConsoleTerminateEvent(array('name' => 'test:run'), 0));
}
public function testGetSubscribedEvents()
{
$this->assertEquals(
array(
'console.exception' => array('onKernelException', -128),
'console.terminate' => array('onKernelTerminate', -128),
),
ExceptionListener::getSubscribedEvents()
);
}
private function getLogger()
{
return $this->getMockForAbstractClass(LoggerInterface::class);
}
private function getConsoleExceptionEvent(\Exception $exception, $input, $exitCode)
{
return new ConsoleExceptionEvent(new Command('test:run'), new ArrayInput($input), new TestOutput(), $exception, $exitCode);
}
private function getConsoleTerminateEvent($input, $exitCode)
{
return new ConsoleTerminateEvent(new Command('test:run'), new ArrayInput($input), new TestOutput(), $exitCode);
}
}