diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 806a06197cafc..9fd21a6d558a6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -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
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
new file mode 100644
index 0000000000000..62d408977c3b9
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Component/Console/EventListener/ExceptionListener.php b/src/Symfony/Component/Console/EventListener/ExceptionListener.php
new file mode 100644
index 0000000000000..afc09c356f4ac
--- /dev/null
+++ b/src/Symfony/Component/Console/EventListener/ExceptionListener.php
@@ -0,0 +1,90 @@
+
+ *
+ * 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
+ */
+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),
+ );
+ }
+}
diff --git a/src/Symfony/Component/Console/Tests/EventListener/ExceptionListenerTest.php b/src/Symfony/Component/Console/Tests/EventListener/ExceptionListenerTest.php
new file mode 100644
index 0000000000000..76c414e935205
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/EventListener/ExceptionListenerTest.php
@@ -0,0 +1,98 @@
+
+ *
+ * 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);
+ }
+}