Skip to content

[WIP] [Console] Add basic support for automatic console exception logging #19382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('web.xml');
$loader->load('services.xml');
$loader->load('fragment_renderer.xml');
$loader->load('console.xml');

// A translator must always be registered (as support is included by
// default in the Form component). If disabled, an identity translator
Expand Down
13 changes: 13 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
Original file line number Diff line number Diff line change
@@ -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>
90 changes: 90 additions & 0 deletions src/Symfony/Component/Console/EventListener/ExceptionListener.php
Original file line number Diff line number Diff line change
@@ -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),
);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}