Skip to content

Commit a2202d7

Browse files
committed
Merge branch 'console-logging' of github.com:kostiklv/symfony-docs into kostiklv-console-logging
Conflicts: cookbook/console/index.rst cookbook/map.rst.inc
2 parents 26e85cf + eea9f7b commit a2202d7

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed

cookbook/console/index.rst

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ Console
77
console_command
88
usage
99
sending_emails
10+
generating_urls
11+
logging

cookbook/console/logging.rst

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
.. index::
2+
single: Console; Enabling logging
3+
4+
How to enable logging in Console Commands
5+
=========================================
6+
7+
The Console component doesn't provide any logging capabilities out of the box.
8+
Normally, you run console commands manually and observe the output, that's
9+
why logging is not provided. However, there are cases when you might need
10+
logging. For example, if you are running console commands unattended, such
11+
as from cron jobs or deployment scripts it may be easier to use Symfony's
12+
logging capabilities instead of configuring other tools to gather console
13+
output and process it. This can be especially handful if you already have
14+
some existing setup for aggregating and analyzing Symfony logs.
15+
16+
There are basically two logging cases you would need:
17+
* Manually logging some information from your command;
18+
* Logging uncaught Exceptions.
19+
20+
Manually logging from console command
21+
-------------------------------------
22+
23+
This one is really simple. When you create console command within full framewok
24+
as described :doc:`here</cookbook/console/console_command>`, your command
25+
extends :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`,
26+
so you can simply access standard logger service through the container and
27+
use it to do the logging::
28+
29+
// src/Acme/DemoBundle/Command/GreetCommand.php
30+
namespace Acme\DemoBundle\Command;
31+
32+
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
33+
use Symfony\Component\Console\Input\InputArgument;
34+
use Symfony\Component\Console\Input\InputInterface;
35+
use Symfony\Component\Console\Input\InputOption;
36+
use Symfony\Component\Console\Output\OutputInterface;
37+
use Symfony\Component\HttpKernel\Log\LoggerInterface;
38+
39+
class GreetCommand extends ContainerAwareCommand
40+
{
41+
// ...
42+
43+
protected function execute(InputInterface $input, OutputInterface $output)
44+
{
45+
/** @var $logger LoggerInterface */
46+
$logger = $this->getContainer()->get('logger');
47+
48+
$name = $input->getArgument('name');
49+
if ($name) {
50+
$text = 'Hello '.$name;
51+
} else {
52+
$text = 'Hello';
53+
}
54+
55+
if ($input->getOption('yell')) {
56+
$text = strtoupper($text);
57+
$logger->warn('Yelled: '.$text);
58+
}
59+
else {
60+
$logger->info('Greeted: '.$text);
61+
}
62+
63+
$output->writeln($text);
64+
}
65+
}
66+
67+
Depending on the environment you run your command you will get the results
68+
in ``app/logs/dev.log`` or ``app/logs/prod.log``.
69+
70+
Enabling automatic Exceptions logging
71+
-------------------------------------
72+
73+
In order to enable console application to automatically log uncaught exceptions
74+
for all commands you'd need to do something more.
75+
76+
First, you have to extend :class:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application`
77+
class to override its :method:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run`
78+
method, where exception handling should happen::
79+
80+
// src/Acme/DemoBundle/Console/Application.php
81+
namespace Acme\DemoBundle\Console;
82+
83+
use Symfony\Bundle\FrameworkBundle\Console\Application as BaseApplication;
84+
use Symfony\Component\Console\Input\InputInterface;
85+
use Symfony\Component\Console\Output\OutputInterface;
86+
use Symfony\Component\Console\Output\ConsoleOutputInterface;
87+
use Symfony\Component\HttpKernel\Log\LoggerInterface;
88+
use Symfony\Component\HttpKernel\KernelInterface;
89+
use Symfony\Component\Console\Output\ConsoleOutput;
90+
use Symfony\Component\Console\Input\ArgvInput;
91+
92+
class Application extends BaseApplication
93+
{
94+
private $originalAutoExit;
95+
96+
public function __construct(KernelInterface $kernel)
97+
{
98+
parent::__construct($kernel);
99+
$this->originalAutoExit = true;
100+
}
101+
102+
/**
103+
* Runs the current application.
104+
*
105+
* @param InputInterface $input An Input instance
106+
* @param OutputInterface $output An Output instance
107+
*
108+
* @return integer 0 if everything went fine, or an error code
109+
*
110+
* @throws \Exception When doRun returns Exception
111+
*
112+
* @api
113+
*/
114+
public function run(InputInterface $input = null, OutputInterface $output = null)
115+
{
116+
//make parent method throw exceptions, so we can log it
117+
$this->setCatchExceptions(false);
118+
119+
if (null === $input) {
120+
$input = new ArgvInput();
121+
}
122+
123+
if (null === $output) {
124+
$output = new ConsoleOutput();
125+
}
126+
127+
try {
128+
$statusCode = parent::run($input, $output);
129+
} catch (\Exception $e) {
130+
131+
/** @var $logger LoggerInterface */
132+
$logger = $this->getKernel()->getContainer()->get('logger');
133+
134+
$message = sprintf(
135+
'%s: %s (uncaught exception) at %s line %s while running console command `%s`',
136+
get_class($e),
137+
$e->getMessage(),
138+
$e->getFile(),
139+
$e->getLine(),
140+
$this->getCommandName($input)
141+
);
142+
$logger->crit($message);
143+
144+
if ($output instanceof ConsoleOutputInterface) {
145+
$this->renderException($e, $output->getErrorOutput());
146+
} else {
147+
$this->renderException($e, $output);
148+
}
149+
$statusCode = $e->getCode();
150+
151+
$statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
152+
}
153+
154+
if ($this->originalAutoExit) {
155+
if ($statusCode > 255) {
156+
$statusCode = 255;
157+
}
158+
// @codeCoverageIgnoreStart
159+
exit($statusCode);
160+
// @codeCoverageIgnoreEnd
161+
}
162+
163+
return $statusCode;
164+
}
165+
166+
public function setAutoExit($bool)
167+
{
168+
// parent property is private, so we need to intercept it in setter
169+
$this->originalAutoExit = (Boolean) $bool;
170+
parent::setAutoExit($bool);
171+
}
172+
173+
}
174+
175+
What happens above is we disable exception catching, so that parent run method
176+
would throw the exceptions. When exception is caught, we simple log it by
177+
accessing the ``logger`` service from the service container and then handle
178+
the rest in the same way parent run method does that (Since parent :method:`run<Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run>`
179+
method will not handle exceptions rendering and status code handling when
180+
`catchExceptions` is set to false, it has to be done in the overridden
181+
method).
182+
183+
For our extended Application class to work properly with console shell mode
184+
we have to do a small trick to intercept ``autoExit`` setter, and store the
185+
setting in a different property, since the parent property is private.
186+
187+
Now to be able to use our extended ``Application`` class we need to adjust
188+
``app/console`` script to use our class instead of the default::
189+
190+
// app/console
191+
192+
// ...
193+
// replace the following line:
194+
// use Symfony\Bundle\FrameworkBundle\Console\Application;
195+
use Acme\DemoBundle\Console\Application;
196+
197+
// ...
198+
199+
That's it! Thanks to autoloader, our class will now be used instead of original
200+
one.
201+
202+
203+
Logging non-0 exit statuses
204+
---------------------------
205+
206+
The logging capabilities of the console can be further extended by logging
207+
non-0 exit statuses. This way you will know if a command had any errors, even
208+
if no exceptions were thrown.
209+
210+
In order to do that, you'd have to modify ``run()`` method of your extended
211+
`Application` class in the following way::
212+
213+
public function run(InputInterface $input = null, OutputInterface $output = null)
214+
{
215+
//make parent method throw exceptions, so we can log it
216+
$this->setCatchExceptions(false);
217+
218+
// store autoExit value before resetting it - we'd need it later
219+
$autoExit = $this->originalAutoExit;
220+
$this->setAutoExit(false);
221+
222+
// ...
223+
224+
if ($autoExit) {
225+
if ($statusCode > 255) {
226+
$statusCode = 255;
227+
}
228+
229+
// log non-0 exit codes along with command name
230+
if ($statusCode !== 0) {
231+
/** @var $logger LoggerInterface */
232+
$logger = $this->getKernel()->getContainer()->get('logger');
233+
$logger->warn(sprintf('Command `%s` exited with status code %d', $this->getCommandName($input), $statusCode));
234+
}
235+
236+
// @codeCoverageIgnoreStart
237+
exit($statusCode);
238+
// @codeCoverageIgnoreEnd
239+
}
240+
241+
return $statusCode;
242+
}
243+
244+
245+

cookbook/map.rst.inc

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
* :doc:`/cookbook/console/console_command`
3030
* :doc:`/cookbook/console/usage`
3131
* :doc:`/cookbook/console/sending_emails`
32+
* :doc:`/cookbook/console/generating_urls`
33+
* :doc:`/cookbook/console/logging`
3234

3335
* :doc:`/cookbook/controller/index`
3436

0 commit comments

Comments
 (0)