Skip to content

Commit 1e84cb0

Browse files
tvlooyweaverryan
authored andcommitted
Rewrite docs
1 parent 34eceb1 commit 1e84cb0

File tree

1 file changed

+180
-155
lines changed

1 file changed

+180
-155
lines changed

cookbook/console/logging.rst

Lines changed: 180 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ container and use it to do the logging::
3434
use Symfony\Component\Console\Input\InputInterface;
3535
use Symfony\Component\Console\Input\InputOption;
3636
use Symfony\Component\Console\Output\OutputInterface;
37-
use Symfony\Component\HttpKernel\Log\LoggerInterface;
37+
use \Psr\Log\LoggerInterface;
3838

3939
class GreetCommand extends ContainerAwareCommand
4040
{
@@ -54,7 +54,7 @@ container and use it to do the logging::
5454

5555
if ($input->getOption('yell')) {
5656
$text = strtoupper($text);
57-
$logger->warn('Yelled: '.$text);
57+
$logger->warning('Yelled: '.$text);
5858
} else {
5959
$logger->info('Greeted: '.$text);
6060
}
@@ -69,146 +69,108 @@ setup), you should see the logged entries in ``app/logs/dev.log`` or ``app/logs/
6969
Enabling automatic Exceptions logging
7070
-------------------------------------
7171

72-
To get your console application to automatically log uncaught exceptions
73-
for all of your commands, you'll need to do a little bit more work.
74-
75-
First, create a new sub-class of :class:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application`
76-
and override its :method:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run`
77-
method, where exception handling should happen:
78-
79-
.. caution::
80-
81-
Due to the nature of the core :class:`Symfony\\Component\\Console\\Application`
82-
class, much of the :method:`run <Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run>`
83-
method has to be duplicated and even a private property ``originalAutoExit``
84-
re-implemented. This serves as an example of what you *could* do in your
85-
code, though there is a high risk that something may break when upgrading
86-
to future versions of Symfony.
87-
88-
.. code-block:: php
89-
90-
// src/Acme/DemoBundle/Console/Application.php
91-
namespace Acme\DemoBundle\Console;
92-
93-
use Symfony\Bundle\FrameworkBundle\Console\Application as BaseApplication;
94-
use Symfony\Component\Console\Input\InputInterface;
95-
use Symfony\Component\Console\Output\OutputInterface;
96-
use Symfony\Component\Console\Output\ConsoleOutputInterface;
97-
use Symfony\Component\HttpKernel\Log\LoggerInterface;
98-
use Symfony\Component\HttpKernel\KernelInterface;
99-
use Symfony\Component\Console\Output\ConsoleOutput;
100-
use Symfony\Component\Console\Input\ArgvInput;
101-
102-
class Application extends BaseApplication
72+
To get your console application to automatically log uncaught exceptions for
73+
all of your commands, you can use :doc:`console events</components/console/events>`.
74+
75+
.. versionadded:: 2.3
76+
Console events were added in Symfony 2.3.
77+
78+
First configure a listener for console exception events in the service container:
79+
80+
.. configuration-block::
81+
82+
.. code-block:: yaml
83+
84+
# app/config/services.yml
85+
services:
86+
kernel.listener.command_dispatch:
87+
class: Acme\DemoBundle\EventListener\ConsoleExceptionListener
88+
arguments:
89+
logger: "@logger"
90+
tags:
91+
- { name: kernel.event_listener, event: console.exception }
92+
93+
.. code-block:: xml
94+
95+
<!-- app/config/services.xml -->
96+
<?xml version="1.0" encoding="UTF-8" ?>
97+
<container xmlns="http://symfony.com/schema/dic/services"
98+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
99+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
100+
101+
<parameters>
102+
<parameter key="console_exception_listener.class">Acme\DemoBundle\EventListener\ConsoleExceptionListener</parameter>
103+
</parameters>
104+
105+
<services>
106+
<service id="kernel.listener.command_dispatch" class="%console_exception_listener.class%">
107+
<argument type="service" id="logger"/>
108+
<tag name="kernel.event_listener" event="console.exception" />
109+
</service>
110+
</services>
111+
</container>
112+
113+
.. code-block:: php
114+
115+
// app/config/services.php
116+
use Symfony\Component\DependencyInjection\Definition;
117+
use Symfony\Component\DependencyInjection\Reference;
118+
119+
$container->setParameter(
120+
'console_exception_listener.class',
121+
'Acme\DemoBundle\EventListener\ConsoleExceptionListener'
122+
);
123+
$definitionConsoleExceptionListener = new Definition(
124+
'%console_exception_listener.class%',
125+
array(new Reference('logger'))
126+
);
127+
$definitionConsoleExceptionListener->addTag(
128+
'kernel.event_listener',
129+
array('event' => 'console.exception')
130+
);
131+
$container->setDefinition(
132+
'kernel.listener.command_dispatch',
133+
$definitionConsoleExceptionListener
134+
);
135+
136+
Then implement the actual listener::
137+
138+
// src/Acme/DemoBundle/EventListener/ConsoleExceptionListener.php
139+
namespace Acme\DemoBundle\EventListener;
140+
141+
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
142+
use \Psr\Log\LoggerInterface;
143+
144+
class ConsoleExceptionListener
103145
{
104-
private $originalAutoExit;
146+
/** @var $logger LoggerInterface */
147+
private $logger;
105148

106-
public function __construct(KernelInterface $kernel)
149+
public function __construct(LoggerInterface $logger)
107150
{
108-
parent::__construct($kernel);
109-
$this->originalAutoExit = true;
151+
$this->logger = $logger;
110152
}
111153

112-
/**
113-
* Runs the current application.
114-
*
115-
* @param InputInterface $input An Input instance
116-
* @param OutputInterface $output An Output instance
117-
*
118-
* @return integer 0 if everything went fine, or an error code
119-
*
120-
* @throws \Exception When doRun returns Exception
121-
*
122-
* @api
123-
*/
124-
public function run(InputInterface $input = null, OutputInterface $output = null)
125-
{
126-
// make the parent method throw exceptions, so you can log it
127-
$this->setCatchExceptions(false);
128-
129-
if (null === $input) {
130-
$input = new ArgvInput();
131-
}
132-
133-
if (null === $output) {
134-
$output = new ConsoleOutput();
135-
}
136-
137-
try {
138-
$statusCode = parent::run($input, $output);
139-
} catch (\Exception $e) {
140-
141-
/** @var $logger LoggerInterface */
142-
$logger = $this->getKernel()->getContainer()->get('logger');
143-
144-
$message = sprintf(
145-
'%s: %s (uncaught exception) at %s line %s while running console command `%s`',
146-
get_class($e),
147-
$e->getMessage(),
148-
$e->getFile(),
149-
$e->getLine(),
150-
$this->getCommandName($input)
151-
);
152-
$logger->crit($message);
153-
154-
if ($output instanceof ConsoleOutputInterface) {
155-
$this->renderException($e, $output->getErrorOutput());
156-
} else {
157-
$this->renderException($e, $output);
158-
}
159-
$statusCode = $e->getCode();
160-
161-
$statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
162-
}
163-
164-
if ($this->originalAutoExit) {
165-
if ($statusCode > 255) {
166-
$statusCode = 255;
167-
}
168-
// @codeCoverageIgnoreStart
169-
exit($statusCode);
170-
// @codeCoverageIgnoreEnd
171-
}
154+
public function onConsoleException(ConsoleExceptionEvent $event) {
155+
$command = $event->getCommand();
156+
$exception = $event->getException();
172157

173-
return $statusCode;
174-
}
158+
$message = sprintf(
159+
'%s: %s (uncaught exception) at %s line %s while running console command `%s`',
160+
get_class($exception),
161+
$exception->getMessage(),
162+
$exception->getFile(),
163+
$exception->getLine(),
164+
$command->getName()
165+
);
175166

176-
public function setAutoExit($bool)
177-
{
178-
// parent property is private, so we need to intercept it in a setter
179-
$this->originalAutoExit = (Boolean) $bool;
180-
parent::setAutoExit($bool);
167+
$this->logger->error($message);
181168
}
182-
183169
}
184170

185-
In the code above, you disable exception catching so the parent ``run`` method
186-
will throw all exceptions. When an exception is caught, you simply log it by
187-
accessing the ``logger`` service from the service container and then handle
188-
the rest of the logic in the same way that the parent ``run`` method does
189-
(specifically, since the parent :method:`run <Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run>`
190-
method will not handle exceptions rendering and status code handling when
191-
``catchExceptions`` is set to false, it has to be done in the overridden
192-
method).
193-
194-
For the extended ``Application`` class to work properly with in console shell mode,
195-
you have to do a small trick to intercept the ``autoExit`` setter and store the
196-
setting in a different property, since the parent property is private.
197-
198-
Now to be able to use your extended ``Application`` class you need to adjust
199-
the ``app/console`` script to use the new class instead of the default::
200-
201-
// app/console
202-
203-
// ...
204-
// replace the following line:
205-
// use Symfony\Bundle\FrameworkBundle\Console\Application;
206-
use Acme\DemoBundle\Console\Application;
207-
208-
// ...
209-
210-
That's it! Thanks to autoloader, your class will now be used instead of original
211-
one.
171+
In the code above, when a command throws an exception, the listener will
172+
receive an event. You can simply log it by passing the logger service via the
173+
service configuration.
212174

213175
Logging non-0 exit statuses
214176
---------------------------
@@ -217,36 +179,99 @@ The logging capabilities of the console can be further extended by logging
217179
non-0 exit statuses. This way you will know if a command had any errors, even
218180
if no exceptions were thrown.
219181

220-
In order to do that, you'd have to modify the ``run()`` method of your extended
221-
``Application`` class in the following way::
222-
223-
public function run(InputInterface $input = null, OutputInterface $output = null)
182+
First configure a listener for console terminate events in the service container:
183+
184+
.. configuration-block::
185+
186+
.. code-block:: yaml
187+
188+
# app/config/services.yml
189+
services:
190+
kernel.listener.command_dispatch:
191+
class: Acme\DemoBundle\EventListener\ConsoleTerminateListener
192+
arguments:
193+
logger: "@logger"
194+
tags:
195+
- { name: kernel.event_listener, event: console.terminate }
196+
197+
.. code-block:: xml
198+
199+
<!-- app/config/services.xml -->
200+
<?xml version="1.0" encoding="UTF-8" ?>
201+
<container xmlns="http://symfony.com/schema/dic/services"
202+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
203+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
204+
205+
<parameters>
206+
<parameter key="console_terminate_listener.class">Acme\DemoBundle\EventListener\ConsoleExceptionListener</parameter>
207+
</parameters>
208+
209+
<services>
210+
<service id="kernel.listener.command_dispatch" class="%console_terminate_listener.class%">
211+
<argument type="service" id="logger"/>
212+
<tag name="kernel.event_listener" event="console.terminate" />
213+
</service>
214+
</services>
215+
</container>
216+
217+
.. code-block:: php
218+
219+
// app/config/services.php
220+
use Symfony\Component\DependencyInjection\Definition;
221+
use Symfony\Component\DependencyInjection\Reference;
222+
223+
$container->setParameter(
224+
'console_terminate_listener.class',
225+
'Acme\DemoBundle\EventListener\ConsoleExceptionListener'
226+
);
227+
$definitionConsoleExceptionListener = new Definition(
228+
'%console_terminate_listener.class%',
229+
array(new Reference('logger'))
230+
);
231+
$definitionConsoleExceptionListener->addTag(
232+
'kernel.event_listener',
233+
array('event' => 'console.terminate')
234+
);
235+
$container->setDefinition(
236+
'kernel.listener.command_dispatch',
237+
$definitionConsoleExceptionListener
238+
);
239+
240+
Then implement the actual listener::
241+
242+
// src/Acme/DemoBundle/EventListener/ConsoleExceptionListener.php
243+
namespace Acme/DemoBundle\EventListener;
244+
245+
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
246+
use \Psr\Log\LoggerInterface;
247+
248+
class ConsoleTerminateListener
224249
{
225-
// make the parent method throw exceptions, so you can log it
226-
$this->setCatchExceptions(false);
250+
/** @var $logger LoggerInterface */
251+
private $logger;
227252

228-
// store the autoExit value before resetting it - you'll need it later
229-
$autoExit = $this->originalAutoExit;
230-
$this->setAutoExit(false);
253+
public function __construct(LoggerInterface $logger)
254+
{
255+
$this->logger = $logger;
256+
}
231257

232-
// ...
258+
public function onConsoleTerminate(ConsoleTerminateEvent $event) {
259+
$statusCode = $event->getExitCode();
260+
$command = $event->getCommand();
233261

234-
if ($autoExit) {
235-
if ($statusCode > 255) {
236-
$statusCode = 255;
262+
if ($statusCode === 0) {
263+
return;
237264
}
238265

239-
// log non-0 exit codes along with command name
240-
if ($statusCode !== 0) {
241-
/** @var $logger LoggerInterface */
242-
$logger = $this->getKernel()->getContainer()->get('logger');
243-
$logger->warn(sprintf('Command `%s` exited with status code %d', $this->getCommandName($input), $statusCode));
266+
if ($statusCode > 255) {
267+
$statusCode = 255;
268+
$event->setExitCode($statusCode);
244269
}
245270

246-
// @codeCoverageIgnoreStart
247-
exit($statusCode);
248-
// @codeCoverageIgnoreEnd
271+
$this->logger->warning(sprintf(
272+
'Command `%s` exited with status code %d',
273+
$command->getName(),
274+
$statusCode
275+
));
249276
}
250-
251-
return $statusCode;
252277
}

0 commit comments

Comments
 (0)