@@ -34,7 +34,7 @@ container and use it to do the logging::
34
34
use Symfony\Component\Console\Input\InputInterface;
35
35
use Symfony\Component\Console\Input\InputOption;
36
36
use Symfony\Component\Console\Output\OutputInterface;
37
- use Symfony\Component\HttpKernel \Log\LoggerInterface;
37
+ use \Psr \Log\LoggerInterface;
38
38
39
39
class GreetCommand extends ContainerAwareCommand
40
40
{
@@ -54,7 +54,7 @@ container and use it to do the logging::
54
54
55
55
if ($input->getOption('yell')) {
56
56
$text = strtoupper($text);
57
- $logger->warn ('Yelled: '.$text);
57
+ $logger->warning ('Yelled: '.$text);
58
58
} else {
59
59
$logger->info('Greeted: '.$text);
60
60
}
@@ -69,146 +69,108 @@ setup), you should see the logged entries in ``app/logs/dev.log`` or ``app/logs/
69
69
Enabling automatic Exceptions logging
70
70
-------------------------------------
71
71
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
103
145
{
104
- private $originalAutoExit;
146
+ /** @var $logger LoggerInterface */
147
+ private $logger;
105
148
106
- public function __construct(KernelInterface $kernel )
149
+ public function __construct(LoggerInterface $logger )
107
150
{
108
- parent::__construct($kernel);
109
- $this->originalAutoExit = true;
151
+ $this->logger = $logger;
110
152
}
111
153
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();
172
157
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
+ );
175
166
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);
181
168
}
182
-
183
169
}
184
170
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.
212
174
213
175
Logging non-0 exit statuses
214
176
---------------------------
@@ -217,36 +179,99 @@ The logging capabilities of the console can be further extended by logging
217
179
non-0 exit statuses. This way you will know if a command had any errors, even
218
180
if no exceptions were thrown.
219
181
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
224
249
{
225
- // make the parent method throw exceptions, so you can log it
226
- $this->setCatchExceptions(false) ;
250
+ /** @var $logger LoggerInterface */
251
+ private $logger ;
227
252
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
+ }
231
257
232
- // ...
258
+ public function onConsoleTerminate(ConsoleTerminateEvent $event) {
259
+ $statusCode = $event->getExitCode();
260
+ $command = $event->getCommand();
233
261
234
- if ($autoExit) {
235
- if ($statusCode > 255) {
236
- $statusCode = 255;
262
+ if ($statusCode === 0) {
263
+ return;
237
264
}
238
265
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);
244
269
}
245
270
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
+ ));
249
276
}
250
-
251
- return $statusCode;
252
277
}
0 commit comments