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

Skip to content

Commit 1f8ddd9

Browse files
committed
[FrameworkBundle][Monolog] Added a new way to follow logs
1 parent d0e8476 commit 1f8ddd9

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Monolog\Formatter;
13+
14+
use Monolog\Formatter\FormatterInterface;
15+
use Symfony\Component\VarDumper\Cloner\VarCloner;
16+
17+
/**
18+
* @author Grégoire Pineau <[email protected]>
19+
*/
20+
class VarDumperFormatter implements FormatterInterface
21+
{
22+
private $cloner;
23+
24+
public function __construct(VarCloner $cloner = null)
25+
{
26+
$this->cloner = $cloner ?: new VarCloner();
27+
}
28+
29+
public function format(array $record)
30+
{
31+
$record['context'] = $this->cloner->cloneVar($record['context']);
32+
$record['extra'] = $this->cloner->cloneVar($record['extra']);
33+
34+
return $record;
35+
}
36+
37+
public function formatBatch(array $records)
38+
{
39+
foreach ($records as $k => $record) {
40+
$record[$k] = $this->format($record);
41+
}
42+
43+
return $records;
44+
}
45+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\Monolog\Handler;
4+
5+
use Monolog\Handler\AbstractProcessingHandler;
6+
use Monolog\Logger;
7+
use Monolog\Processor\UidProcessor;
8+
use Symfony\Bridge\Monolog\Formatter\VarDumperFormatter;
9+
10+
class ServerLogHandler extends AbstractProcessingHandler
11+
{
12+
protected $formatter;
13+
14+
private $connectionString;
15+
16+
public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
17+
{
18+
parent::__construct($level, $bubble);
19+
20+
$this->connectionString = $connectionString;
21+
$this->formatter = new VarDumperFormatter();
22+
$this->pushProcessor(new UidProcessor());
23+
}
24+
25+
public function handle(array $record)
26+
{
27+
if (!$this->isHandling($record)) {
28+
return false;
29+
}
30+
31+
$record = $this->processRecord($record);
32+
33+
$this->write($record);
34+
35+
return false === $this->bubble;
36+
}
37+
38+
protected function write(array $record)
39+
{
40+
set_error_handler([$this, 'nullErrorHandler']);
41+
$socket = fsockopen($this->connectionString, -1, $errno, $errstr, 1);
42+
restore_error_handler();
43+
44+
if (false === $socket) {
45+
46+
return;
47+
}
48+
49+
$keys = ['id', 'uid', 'uuid', 'log_uuid'];
50+
$logId = null;
51+
foreach ($keys as $key) {
52+
if (isset($record['extra'][$key])) {
53+
$logId = $record['extra'][$key];
54+
}
55+
}
56+
$formatted = $this->getFormatter()->format($record);
57+
$formatted['log_id'] = $logId;
58+
$formatted = base64_encode(serialize($formatted));
59+
60+
fwrite($socket, $formatted);
61+
fclose($socket);
62+
}
63+
64+
private function nullErrorHandler()
65+
{
66+
}
67+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Input\InputOption;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
19+
use Symfony\Component\VarDumper\Cloner\Data;
20+
use Symfony\Component\VarDumper\Dumper\CliDumper;
21+
22+
/**
23+
* @author Grégoire Pineau <[email protected]>
24+
*/
25+
class ServerLogCommand extends Command
26+
{
27+
private $dumper;
28+
private $output;
29+
30+
private static $levelColorMap = [
31+
100 => 'fg=white',
32+
200 => 'fg=green',
33+
250 => 'fg=blue',
34+
300 => 'fg=cyan',
35+
400 => 'fg=yellow',
36+
500 => 'fg=red',
37+
550 => 'fg=red',
38+
600 => 'fg=white;bg=red',
39+
];
40+
41+
private static $bgColor = [
42+
'black',
43+
'red',
44+
'green',
45+
'yellow',
46+
'blue',
47+
'magenta',
48+
'cyan',
49+
'white',
50+
];
51+
52+
protected function configure()
53+
{
54+
$this
55+
->setName('server:log')
56+
->setDescription('Display log')
57+
->addOption('address', null, InputOption::VALUE_REQUIRED, 'The server address', '0:9911')
58+
->addOption('date-format', null, InputOption::VALUE_REQUIRED, 'The date format', 'H:i:s')
59+
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The line format', '%s <%s>%-9s</> <options=bold>%-8.8s</> %s')
60+
->addOption('display-context', null, InputOption::VALUE_NONE, 'Display the context')
61+
->addOption('display-extra', null, InputOption::VALUE_NONE, 'Display the extra')
62+
->addOption('filter', null, InputOption::VALUE_REQUIRED, 'An expression to filter log. Example: record[\'level\'] > 200 or record[\'channel\'] in [\'app\', \'doctrine\']"')
63+
;
64+
}
65+
66+
protected function initialize(InputInterface $input, OutputInterface $output)
67+
{
68+
$this->dumper = new CliDumper();
69+
$this->dumper->setOutput($this->output = fopen('php://memory', 'r+b'));
70+
71+
$this->el = new ExpressionLanguage();
72+
}
73+
74+
protected function execute(InputInterface $input, OutputInterface $output)
75+
{
76+
$logs = $this->getLogs($input);
77+
78+
$filter = $input->getOption('filter');
79+
80+
foreach ($logs as $message) {
81+
$record = unserialize(base64_decode($message));
82+
83+
// Impossible to decode the message, give up.
84+
if (false === $record) {
85+
continue;
86+
}
87+
88+
if ($filter && !$this->el->evaluate($filter, ['record' => $record])) {
89+
continue;
90+
}
91+
92+
$this->displayLog($input, $output, $record);
93+
}
94+
}
95+
96+
private function getLogs(InputInterface $input)
97+
{
98+
$socket = stream_socket_server($input->getOption('address'), $errno, $errstr);
99+
100+
if (!$socket) {
101+
throw new \RuntimeException(sprintf('Impossible to start the server: "%s (%s)".', $errstr, $errno));
102+
}
103+
104+
while ($client = stream_socket_accept($socket, -1)) {
105+
$message = '';
106+
107+
while (false !== ($buffer = fgets($client, 65535))) {
108+
$message .= $buffer;
109+
}
110+
111+
// Impossible to read the whole message, give up.
112+
if (!feof($client)) {
113+
fclose($client);
114+
115+
continue;
116+
}
117+
118+
fclose($client);
119+
120+
yield $message;
121+
}
122+
}
123+
124+
private function displayLog(InputInterface $input, OutputInterface $output, array $record)
125+
{
126+
$record = $this->replacePlaceHolder($output, $record);
127+
128+
$format = $input->getOption('format');
129+
$date = $record['datetime']->format($input->getOption('date-format'));
130+
$logBlock = '';
131+
if ($record['log_id']) {
132+
$clientId = unpack('H*', $record['log_id'])[1];
133+
$logBlock = sprintf('<bg=%s> </>', self::$bgColor[$clientId % 8]);
134+
}
135+
$levelColor = self::$levelColorMap[$record['level']];
136+
$message = $logBlock.sprintf($format, $date, $levelColor, $record['level_name'], $record['channel'], $record['message']);
137+
138+
$output->writeln($message);
139+
140+
if ($input->getOption('display-context') && $record['context']->getRawData()[0][0]) {
141+
$output->writeln(sprintf('%sContext %s', $logBlock, $this->dumpData($output, $record['context'])));
142+
}
143+
if ($input->getOption('display-extra') && $record['extra']->getRawData()[0][0]) {
144+
$output->writeln(sprintf('%sExtra %s', $logBlock, $this->dumpData($output, $record['extra'])));
145+
}
146+
}
147+
148+
private function replacePlaceHolder(OutputInterface $output, array $record)
149+
{
150+
$message = $record['message'];
151+
152+
if (false === strpos($message, '{')) {
153+
return $record;
154+
}
155+
156+
$context = $record['context'];
157+
158+
$replacements = array();
159+
foreach ($context->getRawData()[1] as $k => $v) {
160+
$replacements['"{'.$k.'}"'] = sprintf('<comment>%s</>', $this->dumpData($output, $context->seek($k), false));
161+
}
162+
163+
$record['message'] = strtr($message, $replacements);
164+
165+
return $record;
166+
}
167+
168+
public function dumpData(OutputInterface $output, Data $data, $colors = true, $maxDepth = 0)
169+
{
170+
if ($output->isDecorated()) {
171+
$this->dumper->setColors($colors);
172+
}
173+
174+
$this->dumper->dump($data);
175+
176+
$dump = stream_get_contents($this->output, -1, 0);
177+
rewind($this->output);
178+
ftruncate($this->output, 0);
179+
180+
return rtrim($dump);
181+
}
182+
}

0 commit comments

Comments
 (0)