Skip to content

Commit

Permalink
feature #21705 [Bridge/Monolog] Enhance the Console Handler (lyrixx)
Browse files Browse the repository at this point in the history
This PR was merged into the 3.3-dev branch.

Discussion
----------

[Bridge/Monolog] Enhance the Console Handler

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | -
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

---

I extracted and enhanced the formatting logic from #21080.
Basically, The formatter now use the VarDumper & use more significant colors and has a more compact / readable format (IMHO).

---

I used the following code to generate before/after screenshots.

```php
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $logger = $this->getContainer()->get('logger');
        $filesystem = $this->getContainer()->get('filesystem');

        $anObject = new \stdClass;
        $anObject->firstPpt = 'foo';
        $anObject->secondePpt = 'bar';

        $logger->log('notice', 'Hello {who}', [
            'who' => 'Wold',
            'an_object' => $anObject,
            'file_system' => $filesystem,
        ]);

        $r = new \ReflectionClass(LogLevel::class);
        foreach ($r->getConstants() as $level) {
            $logger = $logger->withName($level);
            $logger->log($level, 'log at level {level}', [
                'level' => $level,
            ]);
        }
    }
```

before:

![screenshot12](https://cloud.githubusercontent.com/assets/408368/23183075/0bb21f80-f87b-11e6-8123-f6da70f2493b.png)

After:

![screenshot11](https://cloud.githubusercontent.com/assets/408368/23182971/b4022ed8-f87a-11e6-9d3b-de1a9d4ce9aa.png)

Commits
-------

b663ab5 [Bridge/Monolog] Enhanced the Console Handler
  • Loading branch information
fabpot committed Mar 6, 2017
2 parents abeb86b + b663ab5 commit a42cf1b
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 21 deletions.
5 changes: 5 additions & 0 deletions src/Symfony/Bridge/Monolog/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

3.3.0
-----

* Improved the console handler output formatting by adding var-dumper support

3.0.0
-----

Expand Down
189 changes: 172 additions & 17 deletions src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php
Expand Up @@ -11,45 +11,200 @@

namespace Symfony\Bridge\Monolog\Formatter;

use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
* Formats incoming records for console output by coloring them depending on log level.
*
* @author Tobias Schultze <http://tobion.de>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ConsoleFormatter extends LineFormatter
class ConsoleFormatter implements FormatterInterface
{
const SIMPLE_FORMAT = "%start_tag%[%datetime%] %channel%.%level_name%:%end_tag% %message% %context% %extra%\n";
const SIMPLE_FORMAT = "%datetime% %start_tag%%level_name%%end_tag% <comment>[%channel%]</> %message%%context%%extra%\n";
const SIMPLE_DATE = 'H:i:s';

private static $levelColorMap = array(
Logger::DEBUG => 'fg=white',
Logger::INFO => 'fg=green',
Logger::NOTICE => 'fg=blue',
Logger::WARNING => 'fg=cyan',
Logger::ERROR => 'fg=yellow',
Logger::CRITICAL => 'fg=red',
Logger::ALERT => 'fg=red',
Logger::EMERGENCY => 'fg=white;bg=red',
);

private $options;
private $cloner;
private $outputBuffer;
private $dumper;

/**
* Constructor.
*
* Available options:
* * format: The format of the outputted log string. The following placeholders are supported: %datetime%, %start_tag%, %level_name%, %end_tag%, %channel%, %message%, %context%, %extra%;
* * date_format: The format of the outputted date string;
* * colors: If true, the log string contains ANSI code to add color;
* * multiline: If false, "context" and "extra" are dumped on one line.
*/
public function __construct($options = array())
{
// BC Layer
if (!is_array($options)) {
@trigger_error(sprintf('The constructor arguments $format, $dateFormat, $allowInlineLineBreaks, $ignoreEmptyContextAndExtra of "%s" are deprecated since 3.3 and will be removed in 4.0. Use $options instead.', self::class), E_USER_DEPRECATED);
$args = func_get_args();
$options = array();
if (isset($args[0])) {
$options['format'] = $args[0];
}
if (isset($args[1])) {
$options['date_format'] = $args[1];
}
}

$this->options = array_replace(array(
'format' => self::SIMPLE_FORMAT,
'date_format' => self::SIMPLE_DATE,
'colors' => true,
'multiline' => false,
), $options);

if (class_exists(VarCloner::class)) {
$this->cloner = new VarCloner();
$this->cloner->addCasters(array(
'*' => array($this, 'castObject'),
));

$this->outputBuffer = fopen('php://memory', 'r+b');
if ($this->options['multiline']) {
$output = $this->outputBuffer;
} else {
$output = array($this, 'echoLine');
}

$this->dumper = new CliDumper($output, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
}
}

/**
* {@inheritdoc}
*/
public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = true)
public function formatBatch(array $records)
{
parent::__construct($format, $dateFormat, $allowInlineLineBreaks, $ignoreEmptyContextAndExtra);
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}

return $records;
}

/**
* {@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>';
$record = $this->replacePlaceHolder($record);

$levelColor = self::$levelColorMap[$record['level']];

if ($this->options['multiline']) {
$context = $extra = "\n";
} else {
$context = $extra = ' ';
}
$context .= $this->dumpData($record['context']);
$extra .= $this->dumpData($record['extra']);

$formatted = strtr($this->options['format'], array(
'%datetime%' => $record['datetime']->format($this->options['date_format']),
'%start_tag%' => sprintf('<%s>', $levelColor),
'%level_name%' => sprintf('%-9s', $record['level_name']),
'%end_tag%' => '</>',
'%channel%' => $record['channel'],
'%message%' => $this->replacePlaceHolder($record)['message'],
'%context%' => $context,
'%extra%' => $extra,
));

return $formatted;
}

/**
* @internal
*/
public function echoLine($line, $depth, $indentPad)
{
if (-1 !== $depth) {
fwrite($this->outputBuffer, $line);
}
}

/**
* @internal
*/
public function castObject($v, array $a, Stub $s, $isNested)
{
if ($this->options['multiline']) {
return $a;
}

if ($isNested && !$v instanceof \DateTimeInterface) {
$s->cut = -1;
$a = array();
}

return $a;
}

private function replacePlaceHolder(array $record)
{
$message = $record['message'];

if (false === strpos($message, '{')) {
return $record;
}

$context = $record['context'];

$replacements = array();
foreach ($context as $k => $v) {
$replacements['{'.$k.'}'] = sprintf('<comment>%s</>', $this->dumpData($v, false));
}

$record['message'] = strtr($message, $replacements);

return $record;
}

private function dumpData($data, $colors = null)
{
if (null === $this->dumper) {
return '';
}

if (null === $colors) {
$this->dumper->setColors($this->options['colors']);
} else {
$record['start_tag'] = '';
$record['end_tag'] = '';
$this->dumper->setColors($colors);
}

return parent::format($record);
if (!$data instanceof Data) {
$data = $this->cloner->cloneVar($data);
}
$data = $data->withRefHandles(false);
$this->dumper->dump($data);

$dump = stream_get_contents($this->outputBuffer, -1, 0);
rewind($this->outputBuffer);
ftruncate($this->outputBuffer, 0);

return rtrim($dump);
}
}
9 changes: 8 additions & 1 deletion src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php
Expand Up @@ -164,7 +164,14 @@ protected function write(array $record)
*/
protected function getDefaultFormatter()
{
return new ConsoleFormatter();
if (!$this->output) {
return new ConsoleFormatter();
}

return new ConsoleFormatter(array(
'colors' => $this->output->isDecorated(),
'multiline' => OutputInterface::VERBOSITY_DEBUG <= $this->output->getVerbosity(),
));
}

/**
Expand Down
10 changes: 8 additions & 2 deletions src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php
Expand Up @@ -59,13 +59,19 @@ public function testVerbosityMapping($verbosity, $level, $isHandling, array $map

// check that the handler actually outputs the record if it handles it
$levelName = Logger::getLevelName($level);
$levelName = sprintf('%-9s', $levelName);

$realOutput = $this->getMockBuilder('Symfony\Component\Console\Output\Output')->setMethods(array('doWrite'))->getMock();
$realOutput->setVerbosity($verbosity);
if ($realOutput->isDebug()) {
$log = "16:21:54 $levelName [app] My info message\n[]\n[]\n";
} else {
$log = "16:21:54 $levelName [app] My info message [] []\n";
}
$realOutput
->expects($isHandling ? $this->once() : $this->never())
->method('doWrite')
->with("[2013-05-29 16:21:54] app.$levelName: My info message \n", false);
->with($log, false);
$handler = new ConsoleHandler($realOutput, true, $map);

$infoRecord = array(
Expand Down Expand Up @@ -143,7 +149,7 @@ public function testWritingAndFormatting()
$output
->expects($this->once())
->method('write')
->with('<info>[2013-05-29 16:21:54] app.INFO:</info> My info message '."\n")
->with("16:21:54 <fg=green>INFO </> <comment>[app]</> My info message\n[]\n[]\n")
;

$handler = new ConsoleHandler(null, false);
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Bridge/Monolog/composer.json
Expand Up @@ -22,7 +22,8 @@
},
"require-dev": {
"symfony/console": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0"
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/var-dumper": "~3.3"
},
"suggest": {
"symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.",
Expand Down

0 comments on commit a42cf1b

Please sign in to comment.