mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Added LogViewer class and CLI command
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
1. [](#new)
|
||||
* Added index file support for Flex Objects
|
||||
* Added `LogViewer` helper class and CLI command: `bin/grav logviewer`
|
||||
1. [](#improved)
|
||||
* Improved error detection for broken Flex Objects
|
||||
* Removed apc and xcache support, made apc alias of apcu
|
||||
|
||||
1
bin/grav
1
bin/grav
@@ -45,5 +45,6 @@ $app->addCommands(array(
|
||||
new \Grav\Console\Cli\NewProjectCommand(),
|
||||
new \Grav\Console\Cli\SchedulerCommand(),
|
||||
new \Grav\Console\Cli\SecurityCommand(),
|
||||
new \Grav\Console\Cli\LogViewerCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
130
system/src/Grav/Common/Helpers/LogViewer.php
Normal file
130
system/src/Grav/Common/Helpers/LogViewer.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Helpers
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
class LogViewer
|
||||
{
|
||||
protected $pattern = '/\[(?P<date>.*)\] (?P<logger>\w+).(?P<level>\w+): (?P<message>.*[^ ]+) (?P<context>[^ ]+) (?P<extra>[^ ]+)/';
|
||||
|
||||
/**
|
||||
* Get the objects of a tailed file
|
||||
*
|
||||
* @param $filepath
|
||||
* @param int $lines
|
||||
* @param bool $desc
|
||||
* @return array
|
||||
*/
|
||||
public function objectTail($filepath, $lines = 1, $desc = true)
|
||||
{
|
||||
$data = $this->tail($filepath, $lines);
|
||||
$tailed_log = explode(PHP_EOL, $data);
|
||||
$line_objects = [];
|
||||
|
||||
foreach ($tailed_log as $line) {
|
||||
$line_objects[] = $this->parse($line);
|
||||
}
|
||||
|
||||
return $desc ? $line_objects : array_reverse($line_objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized way to get just the last few entries of a log file
|
||||
*
|
||||
* @param $filepath
|
||||
* @param int $lines
|
||||
* @return bool|string
|
||||
*/
|
||||
public function tail($filepath, $lines = 1) {
|
||||
|
||||
$f = @fopen($filepath, "rb");
|
||||
if ($f === false) return false;
|
||||
|
||||
else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
|
||||
|
||||
fseek($f, -1, SEEK_END);
|
||||
if (fread($f, 1) != "\n") $lines -= 1;
|
||||
|
||||
// Start reading
|
||||
$output = '';
|
||||
$chunk = '';
|
||||
// While we would like more
|
||||
while (ftell($f) > 0 && $lines >= 0) {
|
||||
// Figure out how far back we should jump
|
||||
$seek = min(ftell($f), $buffer);
|
||||
// Do the jump (backwards, relative to where we are)
|
||||
fseek($f, -$seek, SEEK_CUR);
|
||||
// Read a chunk and prepend it to our output
|
||||
$output = ($chunk = fread($f, $seek)) . $output;
|
||||
// Jump back to where we started reading
|
||||
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
|
||||
// Decrease our line counter
|
||||
$lines -= substr_count($chunk, "\n");
|
||||
}
|
||||
// While we have too many lines
|
||||
// (Because of buffer size we might have read too many)
|
||||
while ($lines++ < 0) {
|
||||
// Find first newline and remove all text before that
|
||||
$output = substr($output, strpos($output, "\n") + 1);
|
||||
}
|
||||
// Close file and return
|
||||
fclose($f);
|
||||
|
||||
return trim($output);
|
||||
}
|
||||
|
||||
public static function levelColor($level)
|
||||
{
|
||||
$colors = [
|
||||
'DEBUG' => 'green',
|
||||
'INFO' => 'cyan',
|
||||
'NOTICE' => 'yellow',
|
||||
'WARNING' => 'yellow',
|
||||
'ERROR' => 'red',
|
||||
'CRITICAL' => 'red',
|
||||
'ALERT' => 'red',
|
||||
'EMERGENCY' => 'magenta'
|
||||
];
|
||||
return $colors[$level] ?? 'white';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a monolog row into array bits
|
||||
*
|
||||
* @param $line
|
||||
* @return array
|
||||
*/
|
||||
public function parse($line)
|
||||
{
|
||||
if( !is_string($line) || strlen($line) === 0) {
|
||||
return array();
|
||||
}
|
||||
preg_match($this->pattern, $line, $data);
|
||||
if (!isset($data['date'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
preg_match('/(.*)- Trace:(.*)/', $data['message'], $matches);
|
||||
if (is_array($matches) && isset($matches[1])) {
|
||||
$data['message'] = trim($matches[1]);
|
||||
$data['trace'] = trim($matches[2]);
|
||||
}
|
||||
|
||||
return array(
|
||||
'date' => \DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
|
||||
'logger' => $data['logger'],
|
||||
'level' => $data['level'],
|
||||
'message' => $data['message'],
|
||||
'trace' => $data['trace'] ?? null,
|
||||
'context' => json_decode($data['context'], true),
|
||||
'extra' => json_decode($data['extra'], true)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
89
system/src/Grav/Console/Cli/LogViewerCommand.php
Normal file
89
system/src/Grav/Console/Cli/LogViewerCommand.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Console\Cli
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\LogViewer;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class LogViewerCommand extends ConsoleCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('logviewer')
|
||||
->addOption(
|
||||
'file',
|
||||
'f',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'custom log file location (default = grav.log)'
|
||||
)
|
||||
->addOption(
|
||||
'lines',
|
||||
'l',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'number of lines (default = 10)'
|
||||
)
|
||||
->setDescription('Display the last few entries of Grav log')
|
||||
->setHelp("Display the last few entries of Grav log");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$grav->setup();
|
||||
|
||||
$file = $this->input->getOption('file') ?? 'grav.log';
|
||||
$lines = $this->input->getOption('lines') ?? 20;
|
||||
$verbose = $this->input->getOption('verbose', false);
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
|
||||
$io->title('Log Viewer');
|
||||
|
||||
$io->writeln(sprintf('viewing last %s entries in <white>%s</white>', $lines, $file));
|
||||
$io->newLine();
|
||||
|
||||
$viewer = new LogViewer();
|
||||
|
||||
$logfile = $grav['locator']->findResource("log://" . $file);
|
||||
|
||||
if ($logfile) {
|
||||
$rows = $viewer->objectTail($logfile, $lines, true);
|
||||
foreach ($rows as $log) {
|
||||
$date = $log['date'];
|
||||
$level_color = LogViewer::levelColor($log['level']);
|
||||
|
||||
if ($date instanceof \DateTime) {
|
||||
$output = "<yellow>{$log['date']->format('Y-m-d h:i:s')}</yellow> [<{$level_color}>{$log['level']}</{$level_color}>]";
|
||||
if ($log['trace'] && $verbose) {
|
||||
$output .= " <white>{$log['message']}</white> - {$log['trace']}";
|
||||
} else {
|
||||
$output .= " {$log['message']}";
|
||||
}
|
||||
$io->writeln($output);
|
||||
if ($verbose) {
|
||||
$io->newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$io->error('cannot find the log file: logs/' . $file);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user