From 664c9b2c8a1b618ea54342a266d6ffb8bf6e4fed Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Sat, 24 Nov 2012 14:58:39 +0100 Subject: [PATCH 1/6] Added cookbook article about console logging --- cookbook/console/index.rst | 1 + cookbook/console/logging.rst | 243 +++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 cookbook/console/logging.rst diff --git a/cookbook/console/index.rst b/cookbook/console/index.rst index e7911e69688..155318529d8 100644 --- a/cookbook/console/index.rst +++ b/cookbook/console/index.rst @@ -7,3 +7,4 @@ Console console_command usage generating_urls + logging diff --git a/cookbook/console/logging.rst b/cookbook/console/logging.rst new file mode 100644 index 00000000000..b5f3a02bd77 --- /dev/null +++ b/cookbook/console/logging.rst @@ -0,0 +1,243 @@ +.. index:: + single: Console; Enabling logging + +How to enable logging in Console Commands +=============================== + +The Console component doesn't provide any logging capabilities out of the box. +Normally, you run console commands manually and observe the output, that's +why logging is not provided. However, there are cases when you might need +logging. For example, if you are running console commands unattended, such +as from cron jobs or deployment scripts it may be easier to use Symfony's +logging capabilities instead of configuring other tools to gather console +output and process it. This can be especially handful if you already have +some existing setup for aggregating and analyzing Symfony logs. + +There are basically two logging cases you would need: + * Manually logging some information from your command + * Logging not caught Exceptions + +Manually logging from console command +---------------------------------- + +This one is really simple. When you create console command within full framewok +as described here (:doc:`/cookbook/console/console_command`), your command +extends (:class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`), +so you can simply access standard logger service through the container and +use it to do the logging:: + + // src/Acme/DemoBundle/Command/GreetCommand.php + + namespace Acme\DemoBundle\Command; + + use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; + use Symfony\Component\Console\Input\InputArgument; + use Symfony\Component\Console\Input\InputInterface; + use Symfony\Component\Console\Input\InputOption; + use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\HttpKernel\Log\LoggerInterface; + + class GreetCommand extends ContainerAwareCommand + { + // .. + + protected function execute(InputInterface $input, OutputInterface $output) + { + /** @var $logger LoggerInterface */ + $logger = $this->getContainer()->get('logger'); + + $name = $input->getArgument('name'); + if ($name) { + $text = 'Hello '.$name; + } else { + $text = 'Hello'; + } + + if ($input->getOption('yell')) { + $text = strtoupper($text); + $logger->warn('Yelled: ' . $text); + } + else { + $logger->info('Greeted: ' . $text); + } + + $output->writeln($text); + } + } + +Depending on the environment you run your command you will get the results +in ``app/dev.log`` or ``app/prod.log``. + +Enabling automatic Exceptions logging +---------------- + +In order to enable console application to automatically log uncaught exceptions +for all commands you'd need to do something more. + +First, you have to extend :class:`Symfony\Bundle\FrameworkBundle\Console\Application` +class to override its ``run()`` method, where exception handling should happen:: + + // src/Acme/DemoBundle/Console/Application.php + + namespace Acme\DemoBundle\Console; + + use Symfony\Bundle\FrameworkBundle\Console\Application as BaseApplication; + use Symfony\Component\Console\Input\InputInterface; + use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Console\Output\ConsoleOutputInterface; + use Symfony\Component\HttpKernel\Log\LoggerInterface; + use Symfony\Component\HttpKernel\KernelInterface; + use Symfony\Component\Console\Output\ConsoleOutput; + use Symfony\Component\Console\Input\ArgvInput; + + class Application extends BaseApplication + { + private $originalAutoExit; + + public function __construct(KernelInterface $kernel) + { + parent::__construct($kernel); + $this->originalAutoExit = true; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return integer 0 if everything went fine, or an error code + * + * @throws \Exception When doRun returns Exception + * + * @api + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + //make parent method throw exceptions, so we can log it + $this->setCatchExceptions(false); + + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + try { + $statusCode = parent::run($input, $output); + } catch (\Exception $e) { + + /** @var $logger LoggerInterface */ + $logger = $this->getKernel()->getContainer()->get('logger'); + + $message = sprintf( + '%s: %s (uncaught exception) at %s line %s while running console command `%s`', + get_class($e), + $e->getMessage(), + $e->getFile(), + $e->getLine(), + $this->getCommandName($input) + ); + $logger->crit($message); + + if ($output instanceof ConsoleOutputInterface) { + $this->renderException($e, $output->getErrorOutput()); + } else { + $this->renderException($e, $output); + } + $statusCode = $e->getCode(); + + $statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1; + } + + if ($this->originalAutoExit) { + if ($statusCode > 255) { + $statusCode = 255; + } + // @codeCoverageIgnoreStart + exit($statusCode); + // @codeCoverageIgnoreEnd + } + + return $statusCode; + } + + public function setAutoExit($bool) + { + // parent property is private, so we need to intercept it in setter + $this->originalAutoExit = (Boolean) $bool; + parent::setAutoExit($bool); + } + + } + +What happens above is we disable exception catching, so that parent run method +would throw the exceptions. When exception is caught, we simple log it by +accessing the ``logger`` service from the service container and then handle +the rest in the same way parent run method does that. + +For our extended Application class to work properly with console shell mode +we have to do a small trick to intercept ``autoExit`` setter, and store the +setting in a different property, since the parent property is private. + +Now to be able to use our extended ``Application`` class we need to adjust +``app/console`` script to use our class instead of the default:: + + // app/console + + // ... + // replace the following line: + // use Symfony\Bundle\FrameworkBundle\Console\Application; + use Acme\DemoBundle\Console\Application; + + // ... + +That's it! Thanks to autoloader, our class will now be used instead of original +one. + + +Logging non-0 exit statuses +------------------------------------------- + +The logging capabilities of the console can be further extended by logging +non-0 exit statuses. This way you will know if a command had any errors, even +if no exceptions were thrown. + +In order to do that, you'd have to modify ``run()`` method of your extended +`Application` class in the following way:: + + public function run(InputInterface $input = null, OutputInterface $output = null) + { + //make parent method throw exceptions, so we can log it + $this->setCatchExceptions(false); + + // store autoExit value before resetting it - we'd need it later + $autoExit = $this->originalAutoExit; + $this->setAutoExit(false); + + // ... + + if ($autoExit) { + if ($statusCode > 255) { + $statusCode = 255; + } + + // log non-0 exit codes along with command name + if ($statusCode !== 0) { + /** @var $logger LoggerInterface */ + $logger = $this->getKernel()->getContainer()->get('logger'); + $logger->warn(sprintf('Command `%s` exited with non 0 status code', $this->getCommandName($input))); + } + + // @codeCoverageIgnoreStart + exit($statusCode); + // @codeCoverageIgnoreEnd + } + + return $statusCode; + } + + + From aaf21763bbb74903c2d0bdda2d8a6bae686fa4b5 Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Sun, 25 Nov 2012 17:02:31 +0200 Subject: [PATCH 2/6] Fixed formatting, improved example of command exit code logging to include the actual exit code --- cookbook/console/logging.rst | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/cookbook/console/logging.rst b/cookbook/console/logging.rst index b5f3a02bd77..0a86a73e491 100644 --- a/cookbook/console/logging.rst +++ b/cookbook/console/logging.rst @@ -2,7 +2,7 @@ single: Console; Enabling logging How to enable logging in Console Commands -=============================== +========================================= The Console component doesn't provide any logging capabilities out of the box. Normally, you run console commands manually and observe the output, that's @@ -14,20 +14,19 @@ output and process it. This can be especially handful if you already have some existing setup for aggregating and analyzing Symfony logs. There are basically two logging cases you would need: - * Manually logging some information from your command - * Logging not caught Exceptions + * Manually logging some information from your command; + * Logging not caught Exceptions. Manually logging from console command ----------------------------------- +------------------------------------- This one is really simple. When you create console command within full framewok -as described here (:doc:`/cookbook/console/console_command`), your command -extends (:class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`), +as described :doc:`here`, your command +extends :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`, so you can simply access standard logger service through the container and use it to do the logging:: // src/Acme/DemoBundle/Command/GreetCommand.php - namespace Acme\DemoBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; @@ -39,7 +38,7 @@ use it to do the logging:: class GreetCommand extends ContainerAwareCommand { - // .. + // ... protected function execute(InputInterface $input, OutputInterface $output) { @@ -55,10 +54,10 @@ use it to do the logging:: if ($input->getOption('yell')) { $text = strtoupper($text); - $logger->warn('Yelled: ' . $text); + $logger->warn('Yelled: '.$text); } else { - $logger->info('Greeted: ' . $text); + $logger->info('Greeted: '.$text); } $output->writeln($text); @@ -69,16 +68,15 @@ Depending on the environment you run your command you will get the results in ``app/dev.log`` or ``app/prod.log``. Enabling automatic Exceptions logging ----------------- +------------------------------------- In order to enable console application to automatically log uncaught exceptions for all commands you'd need to do something more. -First, you have to extend :class:`Symfony\Bundle\FrameworkBundle\Console\Application` -class to override its ``run()`` method, where exception handling should happen:: +First, you have to extend :class:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application` +class to override its :method:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run` method, where exception handling should happen:: // src/Acme/DemoBundle/Console/Application.php - namespace Acme\DemoBundle\Console; use Symfony\Bundle\FrameworkBundle\Console\Application as BaseApplication; @@ -199,7 +197,7 @@ one. Logging non-0 exit statuses -------------------------------------------- +--------------------------- The logging capabilities of the console can be further extended by logging non-0 exit statuses. This way you will know if a command had any errors, even @@ -228,7 +226,7 @@ In order to do that, you'd have to modify ``run()`` method of your extended if ($statusCode !== 0) { /** @var $logger LoggerInterface */ $logger = $this->getKernel()->getContainer()->get('logger'); - $logger->warn(sprintf('Command `%s` exited with non 0 status code', $this->getCommandName($input))); + $logger->warn(sprintf('Command `%s` exited with status code %d', $this->getCommandName($input), $statusCode)); } // @codeCoverageIgnoreStart From 2e4347b95cc6842b6f2a3e5d5c0081299ea5876c Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Sun, 25 Nov 2012 17:51:23 +0200 Subject: [PATCH 3/6] Fixed too long line --- cookbook/console/logging.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cookbook/console/logging.rst b/cookbook/console/logging.rst index 0a86a73e491..ade4b290c6c 100644 --- a/cookbook/console/logging.rst +++ b/cookbook/console/logging.rst @@ -74,7 +74,8 @@ In order to enable console application to automatically log uncaught exceptions for all commands you'd need to do something more. First, you have to extend :class:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application` -class to override its :method:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run` method, where exception handling should happen:: +class to override its :method:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run` +method, where exception handling should happen:: // src/Acme/DemoBundle/Console/Application.php namespace Acme\DemoBundle\Console; From 2088fa4a36e6c684d93e641daf1840020e58fa5b Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Mon, 26 Nov 2012 13:49:37 +0200 Subject: [PATCH 4/6] Fixed style, added explanation on why we are re-implementing part of the parent method --- cookbook/console/logging.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cookbook/console/logging.rst b/cookbook/console/logging.rst index ade4b290c6c..09fa2937d51 100644 --- a/cookbook/console/logging.rst +++ b/cookbook/console/logging.rst @@ -15,7 +15,7 @@ some existing setup for aggregating and analyzing Symfony logs. There are basically two logging cases you would need: * Manually logging some information from your command; - * Logging not caught Exceptions. + * Logging uncaught Exceptions. Manually logging from console command ------------------------------------- @@ -175,7 +175,10 @@ method, where exception handling should happen:: What happens above is we disable exception catching, so that parent run method would throw the exceptions. When exception is caught, we simple log it by accessing the ``logger`` service from the service container and then handle -the rest in the same way parent run method does that. +the rest in the same way parent run method does that (Since parent :method:`run` +method will not handle exceptions rendering and status code handling when +`catchExceptions` is set to false, it has to be done in the overridden +method). For our extended Application class to work properly with console shell mode we have to do a small trick to intercept ``autoExit`` setter, and store the From afd740a2bf81a18d5d490d02a71cbe1efbc05574 Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Mon, 26 Nov 2012 13:55:48 +0200 Subject: [PATCH 5/6] Corrected log folders path --- cookbook/console/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/console/logging.rst b/cookbook/console/logging.rst index 09fa2937d51..a50dbe4eb99 100644 --- a/cookbook/console/logging.rst +++ b/cookbook/console/logging.rst @@ -65,7 +65,7 @@ use it to do the logging:: } Depending on the environment you run your command you will get the results -in ``app/dev.log`` or ``app/prod.log``. +in ``app/logs/dev.log`` or ``app/logs/prod.log``. Enabling automatic Exceptions logging ------------------------------------- From eea9f7b3fc8d1d8ffeca6b64f465019f864c9210 Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Mon, 26 Nov 2012 14:00:14 +0200 Subject: [PATCH 6/6] Added link to console/logging in cookbook map --- cookbook/map.rst.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index 1a8226d0f0c..6bebc460e48 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -29,6 +29,7 @@ * :doc:`/cookbook/console/console_command` * :doc:`/cookbook/console/usage` * :doc:`/cookbook/console/generating_urls` + * :doc:`/cookbook/console/logging` * :doc:`/cookbook/controller/index`