Skip to content

Commit 664c9b2

Browse files
committed
Added cookbook article about console logging
1 parent 9a0b4e1 commit 664c9b2

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

cookbook/console/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Console
77
console_command
88
usage
99
generating_urls
10+
logging

cookbook/console/logging.rst

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

0 commit comments

Comments
 (0)