Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5fa5041

Browse files
committed
[Bridge/Monolog] Enhanced the Console Handler
Basically, the formatter now uses the VarDumper & uses more significant colors and has a more compact / readable format (IMHO).
1 parent 99f60dc commit 5fa5041

File tree

4 files changed

+182
-21
lines changed

4 files changed

+182
-21
lines changed

src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php

Lines changed: 164 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,192 @@
1111

1212
namespace Symfony\Bridge\Monolog\Formatter;
1313

14-
use Monolog\Formatter\LineFormatter;
14+
use Monolog\Formatter\FormatterInterface;
1515
use Monolog\Logger;
16+
use Symfony\Component\VarDumper\Cloner\Data;
17+
use Symfony\Component\VarDumper\Cloner\Stub;
18+
use Symfony\Component\VarDumper\Cloner\VarCloner;
19+
use Symfony\Component\VarDumper\Dumper\CliDumper;
1620

1721
/**
1822
* Formats incoming records for console output by coloring them depending on log level.
1923
*
2024
* @author Tobias Schultze <http://tobion.de>
25+
* @author Grégoire Pineau <[email protected]>
2126
*/
22-
class ConsoleFormatter extends LineFormatter
27+
class ConsoleFormatter implements FormatterInterface
2328
{
24-
const SIMPLE_FORMAT = "%start_tag%[%datetime%] %channel%.%level_name%:%end_tag% %message% %context% %extra%\n";
29+
const SIMPLE_FORMAT = "%datetime% %start_tag%%level_name%%end_tag% <comment>[%channel%]</> %message%%context%%extra%\n";
30+
const SIMPLE_DATE = 'H:i:s';
31+
32+
private static $levelColorMap = array(
33+
Logger::DEBUG => 'fg=white',
34+
Logger::INFO => 'fg=green',
35+
Logger::NOTICE => 'fg=blue',
36+
Logger::WARNING => 'fg=cyan',
37+
Logger::ERROR => 'fg=yellow',
38+
Logger::CRITICAL => 'fg=red',
39+
Logger::ALERT => 'fg=red',
40+
Logger::EMERGENCY => 'fg=white;bg=red',
41+
);
42+
43+
private $options;
44+
private $cloner;
45+
private $outputBuffer;
46+
private $dumper;
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function __construct($options = array())
52+
{
53+
if (!class_exists(VarCloner::class)) {
54+
throw new \RuntimeException('To use the ConsoleFormatter you must install the symfony/var-dumper component.');
55+
}
56+
57+
// BC Layer
58+
if (!is_array($options)) {
59+
@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);
60+
$args = func_get_args();
61+
$options = array();
62+
if (isset($args[0])) {
63+
$options['format'] = $args[0];
64+
}
65+
if (isset($args[1])) {
66+
$options['date_format'] = $args[1];
67+
}
68+
}
69+
70+
$this->options = array_replace(array(
71+
'format' => self::SIMPLE_FORMAT,
72+
'date_format' => self::SIMPLE_DATE,
73+
'colors' => true,
74+
'multiline' => false,
75+
), $options);
76+
77+
$this->cloner = new VarCloner();
78+
$this->cloner->addCasters(array(
79+
'*' => array($this, 'castObject'),
80+
));
81+
82+
$this->outputBuffer = fopen('php://memory', 'r+b');
83+
if ($this->options['multiline']) {
84+
$output = $this->outputBuffer;
85+
} else {
86+
$output = array($this, 'echoLine');
87+
}
88+
89+
$this->dumper = new CliDumper($output, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
90+
}
2591

2692
/**
2793
* {@inheritdoc}
2894
*/
29-
public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = true)
95+
public function formatBatch(array $records)
3096
{
31-
parent::__construct($format, $dateFormat, $allowInlineLineBreaks, $ignoreEmptyContextAndExtra);
97+
foreach ($records as $key => $record) {
98+
$records[$key] = $this->format($record);
99+
}
100+
101+
return $records;
32102
}
33103

34104
/**
35105
* {@inheritdoc}
36106
*/
37107
public function format(array $record)
38108
{
39-
if ($record['level'] >= Logger::ERROR) {
40-
$record['start_tag'] = '<error>';
41-
$record['end_tag'] = '</error>';
42-
} elseif ($record['level'] >= Logger::NOTICE) {
43-
$record['start_tag'] = '<comment>';
44-
$record['end_tag'] = '</comment>';
45-
} elseif ($record['level'] >= Logger::INFO) {
46-
$record['start_tag'] = '<info>';
47-
$record['end_tag'] = '</info>';
109+
$record = $this->replacePlaceHolder($record);
110+
111+
$levelColor = self::$levelColorMap[$record['level']];
112+
113+
if ($this->options['multiline']) {
114+
$context = $extra = "\n";
115+
} else {
116+
$context = $extra = ' ';
117+
}
118+
$context .= $this->dumpData($record['context']);
119+
$extra .= $this->dumpData($record['extra']);
120+
121+
$formatted = strtr($this->options['format'], array(
122+
'%datetime%' => $record['datetime']->format($this->options['date_format']),
123+
'%start_tag%' => sprintf('<%s>', $levelColor),
124+
'%level_name%' => sprintf('%-9s', $record['level_name']),
125+
'%end_tag%' => '</>',
126+
'%channel%' => $record['channel'],
127+
'%message%' => $this->replacePlaceHolder($record)['message'],
128+
'%context%' => $context,
129+
'%extra%' => $extra,
130+
));
131+
132+
return $formatted;
133+
}
134+
135+
/**
136+
* @internal
137+
*/
138+
public function echoLine($line, $depth, $indentPad)
139+
{
140+
if (-1 !== $depth) {
141+
fwrite($this->outputBuffer, $line);
142+
}
143+
}
144+
145+
/**
146+
* @internal
147+
*/
148+
public function castObject($v, array $a, Stub $s, $isNested)
149+
{
150+
if ($this->options['multiline']) {
151+
return $a;
152+
}
153+
154+
if ($isNested && !$v instanceof \DateTimeInterface) {
155+
$s->cut = -1;
156+
$a = array();
157+
}
158+
159+
return $a;
160+
}
161+
162+
private function replacePlaceHolder(array $record)
163+
{
164+
$message = $record['message'];
165+
166+
if (false === strpos($message, '{')) {
167+
return $record;
168+
}
169+
170+
$context = $record['context'];
171+
172+
$replacements = array();
173+
foreach ($context as $k => $v) {
174+
$replacements['{'.$k.'}'] = sprintf('<comment>%s</>', $this->dumpData($v, false));
175+
}
176+
177+
$record['message'] = strtr($message, $replacements);
178+
179+
return $record;
180+
}
181+
182+
private function dumpData($data, $colors = null)
183+
{
184+
if (null === $colors) {
185+
$this->dumper->setColors($this->options['colors']);
48186
} else {
49-
$record['start_tag'] = '';
50-
$record['end_tag'] = '';
187+
$this->dumper->setColors($colors);
51188
}
52189

53-
return parent::format($record);
190+
if (!$data instanceof Data) {
191+
$data = $this->cloner->cloneVar($data);
192+
}
193+
$data = $data->withRefHandles(false);
194+
$this->dumper->dump($data);
195+
196+
$dump = stream_get_contents($this->outputBuffer, -1, 0);
197+
rewind($this->outputBuffer);
198+
ftruncate($this->outputBuffer, 0);
199+
200+
return rtrim($dump);
54201
}
55202
}

src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,14 @@ protected function write(array $record)
164164
*/
165165
protected function getDefaultFormatter()
166166
{
167-
return new ConsoleFormatter();
167+
if (!$this->output) {
168+
return new ConsoleFormatter();
169+
}
170+
171+
return new ConsoleFormatter(array(
172+
'colors' => $this->output->isDecorated(),
173+
'multiline' => OutputInterface::VERBOSITY_DEBUG <= $this->output->getVerbosity(),
174+
));
168175
}
169176

170177
/**

src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,19 @@ public function testVerbosityMapping($verbosity, $level, $isHandling, array $map
5959

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

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

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

149155
$handler = new ConsoleHandler(null, false);

src/Symfony/Bridge/Monolog/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
},
2323
"require-dev": {
2424
"symfony/console": "~2.8|~3.0",
25-
"symfony/event-dispatcher": "~2.8|~3.0"
25+
"symfony/event-dispatcher": "~2.8|~3.0",
26+
"symfony/var-dumper": "~3.3"
2627
},
2728
"suggest": {
2829
"symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.",

0 commit comments

Comments
 (0)