merged branch Tobion/monolog-console-integration (PR #8167)

This PR was squashed before being merged into the master branch (closes #8167).

Discussion
----------

[MonologBridge] added integration with the console component

This adds integration with the console component by adding ConsoleHandler and ConsoleFormatter which can be used to show log messages in the console output depending on the verbosity settings.
This PR splits the reusable classes from symfony/MonologBundle#42 into the bridge.
It has been reviewed by @Seldaek

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #8033
| License       | MIT
| Doc PR        | will be done when this is used by the MonologBundle

Commits
-------

c0b5996 [MonologBridge] added integration with the console component
This commit is contained in:
Fabien Potencier 2013-06-04 17:05:24 +02:00
commit 46b209bc55
5 changed files with 414 additions and 1 deletions

View File

@ -1,6 +1,12 @@
CHANGELOG
=========
2.4.0
-----
* added ConsoleHandler and ConsoleFormatter which can be used to show log messages
in the console output depending on the verbosity settings
2.1.0
-----

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Monolog\Formatter;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
/**
* Formats incoming records for console output by coloring them depending on log level.
*
* @author Tobias Schultze <http://tobion.de>
*/
class ConsoleFormatter extends LineFormatter
{
const SIMPLE_FORMAT = "%start_tag%[%datetime%] %channel%.%level_name%:%end_tag% %message% %context% %extra%\n";
/**
* {@inheritdoc}
*/
public function format(array $record)
{
if ($record['level'] >= Logger::ERROR) {
$record['start_tag'] = '<error>';
$record['end_tag'] = '</error>';
} elseif ($record['level'] >= Logger::NOTICE) {
$record['start_tag'] = '<comment>';
$record['end_tag'] = '</comment>';
} elseif ($record['level'] >= Logger::INFO) {
$record['start_tag'] = '<info>';
$record['end_tag'] = '</info>';
} else {
$record['start_tag'] = '';
$record['end_tag'] = '';
}
return parent::format($record);
}
}

View File

@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Monolog\Handler;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Writes logs to the console output depending on its verbosity setting.
*
* It is disabled by default and gets activated as soon as a command is executed.
* Instead of listening to the console events, the output can also be set manually.
*
* The minimum logging level at which this handler will be triggered depends on the
* verbosity setting of the console output. The default mapping is:
* - OutputInterface::VERBOSITY_NORMAL will show all WARNING and higher logs
* - OutputInterface::VERBOSITY_VERBOSE (-v) will show all NOTICE and higher logs
* - OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) will show all INFO and higher logs
* - OutputInterface::VERBOSITY_DEBUG (-vvv) will show all DEBUG and higher logs, i.e. all logs
*
* This mapping can be customized with the $verbosityLevelMap constructor parameter.
*
* @author Tobias Schultze <http://tobion.de>
*/
class ConsoleHandler extends AbstractProcessingHandler implements EventSubscriberInterface
{
/**
* @var OutputInterface|null
*/
private $output;
/**
* @var array
*/
private $verbosityLevelMap = array(
OutputInterface::VERBOSITY_NORMAL => Logger::WARNING,
OutputInterface::VERBOSITY_VERBOSE => Logger::NOTICE,
OutputInterface::VERBOSITY_VERY_VERBOSE => Logger::INFO,
OutputInterface::VERBOSITY_DEBUG => Logger::DEBUG
);
/**
* Constructor.
*
* @param OutputInterface|null $output The console output to use (the handler remains disabled when passing null
* until the output is set, e.g. by using console events)
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack
* @param array $verbosityLevelMap Array that maps the OutputInterface verbosity to a minimum logging
* level (leave empty to use the default mapping)
*/
public function __construct(OutputInterface $output = null, $bubble = true, array $verbosityLevelMap = array())
{
parent::__construct(Logger::DEBUG, $bubble);
$this->output = $output;
if ($verbosityLevelMap) {
$this->verbosityLevelMap = $verbosityLevelMap;
}
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return $this->updateLevel() && parent::isHandling($record);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
// we have to update the logging level each time because the verbosity of the
// console output might have changed in the meantime (it is not immutable)
return $this->updateLevel() && parent::handle($record);
}
/**
* Sets the console output to use for printing logs.
*
* @param OutputInterface $output The console output to use
*/
public function setOutput(OutputInterface $output)
{
$this->output = $output;
}
/**
* Disables the output.
*/
public function close()
{
$this->output = null;
parent::close();
}
/**
* Before a command is executed, the handler gets activated and the console output
* is set in order to know where to write the logs.
*
* @param ConsoleCommandEvent $event
*/
public function onCommand(ConsoleCommandEvent $event)
{
$this->setOutput($event->getOutput());
}
/**
* After a command has been executed, it disables the output.
*
* @param ConsoleTerminateEvent $event
*/
public function onTerminate(ConsoleTerminateEvent $event)
{
$this->close();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
ConsoleEvents::COMMAND => 'onCommand',
ConsoleEvents::TERMINATE => 'onTerminate'
);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
if ($record['level'] >= Logger::ERROR && $this->output instanceof ConsoleOutputInterface) {
$this->output->getErrorOutput()->write((string) $record['formatted']);
} else {
$this->output->write((string) $record['formatted']);
}
}
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new ConsoleFormatter();
}
/**
* Updates the logging level based on the verbosity setting of the console output.
*
* @return Boolean Whether the handler is enabled and verbosity is not set to quiet.
*/
private function updateLevel()
{
if (null === $this->output || OutputInterface::VERBOSITY_QUIET === $verbosity = $this->output->getVerbosity()) {
return false;
}
if (isset($this->verbosityLevelMap[$verbosity])) {
$this->setLevel($this->verbosityLevelMap[$verbosity]);
} else {
$this->setLevel(Logger::DEBUG);
}
return true;
}
}

View File

@ -0,0 +1,166 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Monolog\Tests\Handler;
use Monolog\Logger;
use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Tests the ConsoleHandler and also the ConsoleFormatter.
*
* @author Tobias Schultze <http://tobion.de>
*/
class ConsoleHandlerTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!class_exists('Monolog\\Logger')) {
$this->markTestSkipped('Monolog is not available.');
}
}
public function testConstructor()
{
$handler = new ConsoleHandler(null, false);
$this->assertFalse($handler->getBubble(), 'the bubble parameter gets propagated');
}
public function testIsHandling()
{
$handler = new ConsoleHandler();
$this->assertFalse($handler->isHandling(array()), '->isHandling returns false when no output is set');
}
/**
* @dataProvider provideVerbosityMappingTests
*/
public function testVerbosityMapping($verbosity, $level, $isHandling, array $map = array())
{
$output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
$output
->expects($this->atLeastOnce())
->method('getVerbosity')
->will($this->returnValue($verbosity))
;
$handler = new ConsoleHandler($output, true, $map);
$this->assertSame($isHandling, $handler->isHandling(array('level' => $level)),
'->isHandling returns correct value depending on console verbosity and log level'
);
}
public function provideVerbosityMappingTests()
{
return array(
array(OutputInterface::VERBOSITY_QUIET, Logger::ERROR, false),
array(OutputInterface::VERBOSITY_NORMAL, Logger::WARNING, true),
array(OutputInterface::VERBOSITY_NORMAL, Logger::NOTICE, false),
array(OutputInterface::VERBOSITY_VERBOSE, Logger::NOTICE, true),
array(OutputInterface::VERBOSITY_VERBOSE, Logger::INFO, false),
array(OutputInterface::VERBOSITY_VERY_VERBOSE, Logger::INFO, true),
array(OutputInterface::VERBOSITY_VERY_VERBOSE, Logger::DEBUG, false),
array(OutputInterface::VERBOSITY_DEBUG, Logger::DEBUG, true),
array(OutputInterface::VERBOSITY_DEBUG, Logger::EMERGENCY, true),
array(OutputInterface::VERBOSITY_NORMAL, Logger::NOTICE, true, array(
OutputInterface::VERBOSITY_NORMAL => Logger::NOTICE
)),
array(OutputInterface::VERBOSITY_DEBUG, Logger::NOTICE, true, array(
OutputInterface::VERBOSITY_NORMAL => Logger::NOTICE
)),
);
}
public function testVerbosityChanged()
{
$output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
$output
->expects($this->at(0))
->method('getVerbosity')
->will($this->returnValue(OutputInterface::VERBOSITY_QUIET))
;
$output
->expects($this->at(1))
->method('getVerbosity')
->will($this->returnValue(OutputInterface::VERBOSITY_DEBUG))
;
$handler = new ConsoleHandler($output);
$this->assertFalse($handler->isHandling(array('level' => Logger::NOTICE)),
'when verbosity is set to quiet, the handler does not handle the log'
);
$this->assertTrue($handler->isHandling(array('level' => Logger::NOTICE)),
'since the verbosity of the output increased externally, the handler is now handling the log'
);
}
public function testGetFormatter()
{
$handler = new ConsoleHandler();
$this->assertInstanceOf('Symfony\Bridge\Monolog\Formatter\ConsoleFormatter', $handler->getFormatter(),
'-getFormatter returns ConsoleFormatter by default'
);
}
public function testWritingAndFormatting()
{
$output = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface');
$output
->expects($this->any())
->method('getVerbosity')
->will($this->returnValue(OutputInterface::VERBOSITY_DEBUG))
;
$output
->expects($this->once())
->method('write')
->with('<info>[2013-05-29 16:21:54] app.INFO:</info> My info message [] []'."\n")
;
$errorOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
$errorOutput
->expects($this->once())
->method('write')
->with('<error>[2013-05-29 16:21:54] app.ERROR:</error> My error message [] []'."\n")
;
$output
->expects($this->any())
->method('getErrorOutput')
->will($this->returnValue($errorOutput))
;
$handler = new ConsoleHandler(null, false);
$handler->setOutput($output);
$infoRecord = array(
'message' => 'My info message',
'context' => array(),
'level' => Logger::INFO,
'level_name' => Logger::getLevelName(Logger::INFO),
'channel' => 'app',
'datetime' => new \DateTime('2013-05-29 16:21:54'),
'extra' => array(),
);
$this->assertTrue($handler->handle($infoRecord), 'The handler finished handling the log as bubble is false.');
$errorRecord = array(
'message' => 'My error message',
'context' => array(),
'level' => Logger::ERROR,
'level_name' => Logger::getLevelName(Logger::ERROR),
'channel' => 'app',
'datetime' => new \DateTime('2013-05-29 16:21:54'),
'extra' => array(),
);
$this->assertTrue($handler->handle($errorRecord), 'The handler finished handling the log as bubble is false.');
}
}

View File

@ -17,9 +17,17 @@
],
"require": {
"php": ">=5.3.3",
"symfony/http-kernel": "~2.2",
"monolog/monolog": "~1.3"
},
"require-dev": {
"symfony/http-kernel": "~2.2",
"symfony/console": "~2.3",
"symfony/event-dispatcher": "~2.2"
},
"suggest": {
"symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.",
"symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it."
},
"autoload": {
"psr-0": { "Symfony\\Bridge\\Monolog\\": "" }
},