Compare commits
4 Commits
77062a9e3e
...
4b82650fe8
Author | SHA1 | Date | |
---|---|---|---|
4b82650fe8 | |||
806d1be9cf | |||
60a7ebb231 | |||
0580cb5d30 |
@ -5,7 +5,7 @@ use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Views\Blade as View;
|
||||
use ProVM\Common\Service\Logs as Service;
|
||||
use ProVM\Logview\Log;
|
||||
use function Safe\json_encode;
|
||||
|
||||
class Logs
|
||||
{
|
||||
@ -13,16 +13,9 @@ class Logs
|
||||
{
|
||||
$log = $service->get($log_file);
|
||||
|
||||
$levels = [];
|
||||
foreach (Log::LEVELS as $level) {
|
||||
$levels[strtolower($level)] = (object) [
|
||||
'text' => Log::COLORS[$level],
|
||||
'background' => Log::BACKGROUNDS[$level],
|
||||
];
|
||||
}
|
||||
return $view->render($response, 'logs.show', compact('log', 'levels'));
|
||||
return $view->render($response, 'logs.show', compact('log'));
|
||||
}
|
||||
public function getMore(ServerRequestInterface $request, ResponseInterface $response, View $view, Service $service, string $log_file, int $start = 0, int $amount = 100): ResponseInterface
|
||||
public function getMore(ServerRequestInterface $request, ResponseInterface $response, Service $service, string $log_file, int $start = 0, int $amount = 100): ResponseInterface
|
||||
{
|
||||
$log = $service->get($log_file);
|
||||
|
||||
@ -32,7 +25,7 @@ class Logs
|
||||
}
|
||||
$logs = array_reverse($logs);
|
||||
$total = $log->getTotal();
|
||||
$response->getBody()->write(\Safe\json_encode([
|
||||
$response->getBody()->write(json_encode([
|
||||
'total' => $total,
|
||||
'logs' => $logs
|
||||
]));
|
||||
|
@ -1,7 +1,30 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Define;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
interface Log
|
||||
{
|
||||
public function getOriginal(): string;
|
||||
public function getDate(): DateTimeInterface;
|
||||
public function getChannel(): string;
|
||||
public function getSeverity(): string;
|
||||
public function getMessage(): string;
|
||||
public function getStack(): array;
|
||||
public function getContext(): string;
|
||||
public function getExtra(): string;
|
||||
|
||||
public function setOriginal(string $original): Log;
|
||||
public function setDate(DateTimeInterface $dateTime): Log;
|
||||
public function setChannel(string $channel): Log;
|
||||
public function setSeverity(string $severity): Log;
|
||||
public function setMessage(string $message): Log;
|
||||
public function setStack(array $stack): Log;
|
||||
public function setContext(string $context): Log;
|
||||
public function setExtra(string $extra): Log;
|
||||
|
||||
public function parsed(): bool;
|
||||
|
||||
public function hasStack(): bool;
|
||||
public function hasContext(): bool;
|
||||
}
|
||||
|
@ -3,5 +3,32 @@ namespace ProVM\Common\Define;
|
||||
|
||||
interface Parser
|
||||
{
|
||||
public function parse(string $content): Log;
|
||||
/**
|
||||
* Determine if file is multiline
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function isMultiline(string $filename): bool;
|
||||
|
||||
/**
|
||||
* Get the total amount of errors
|
||||
* @param string $filename
|
||||
* @return int
|
||||
*/
|
||||
public function total(string $filename): int;
|
||||
|
||||
/**
|
||||
* Parse line(s)
|
||||
* @param mixed &$file_handler
|
||||
* @return Log
|
||||
*/
|
||||
public function parse(mixed &$file_handler): Log;
|
||||
|
||||
/**
|
||||
* Advance $offset errors
|
||||
* @param mixed $file_handler
|
||||
* @param int $offset
|
||||
* @return void
|
||||
*/
|
||||
public function advance(mixed &$file_handler, int $offset): void;
|
||||
}
|
||||
|
@ -1,14 +1,25 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Implement;
|
||||
|
||||
use Exception;
|
||||
use ProVM\Common\Define\Log;
|
||||
use ProVM\Logview\Exception\Parse\EmptyException;
|
||||
use ProVM\Logview\Exception\Parse\EmptyLineException;
|
||||
use ProVM\Logview\Log as LogContent;
|
||||
use ProVM\Common\Define\Parser as Definition;
|
||||
use function Safe\{fopen, fclose};
|
||||
|
||||
abstract class Parser implements \ProVM\Common\Define\Parser
|
||||
abstract class Parser implements Definition
|
||||
{
|
||||
public function isMultiline(string $filename): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function total(string $filename): int
|
||||
{
|
||||
try {
|
||||
$fh = \Safe\fopen($filename, 'r');
|
||||
$fh = fopen($filename, 'r');
|
||||
$cnt = 0;
|
||||
while(!feof($fh)) {
|
||||
$line = fgets($fh);
|
||||
@ -16,12 +27,35 @@ abstract class Parser implements \ProVM\Common\Define\Parser
|
||||
}
|
||||
fclose($fh);
|
||||
return $cnt;
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public function parse(string $content): Log
|
||||
public function parse(mixed &$file_handler): Log
|
||||
{
|
||||
return new \ProVM\Logview\Log($content);
|
||||
$content = fgets($file_handler);
|
||||
if (!$content) {
|
||||
$meta_data = stream_get_meta_data($file_handler);
|
||||
throw new EmptyException($meta_data['uri'], ftell($file_handler));
|
||||
}
|
||||
if (trim($content) === '') {
|
||||
$meta_data = stream_get_meta_data($file_handler);
|
||||
throw new EmptyLineException($meta_data['uri'], ftell($file_handler));
|
||||
}
|
||||
return new LogContent($content);
|
||||
}
|
||||
public function advance(mixed &$file_handler, int $offset): void
|
||||
{
|
||||
if ($offset === 0) {
|
||||
return;
|
||||
}
|
||||
$cnt = 0;
|
||||
while(!feof($file_handler)) {
|
||||
fgets($file_handler);
|
||||
$cnt ++;
|
||||
if ($cnt >= $offset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,14 @@ namespace ProVM\Common\Service;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use SplFileInfo;
|
||||
use FilesystemIterator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ProVM\Logview\Log\File;
|
||||
use ProVM\Common\Define\Parser;
|
||||
use ProVM\Logview\Parser as Parsers;
|
||||
|
||||
use function Safe\{preg_match};
|
||||
|
||||
class Logs
|
||||
{
|
||||
public function __construct(LoggerInterface $logger, string $folder)
|
||||
@ -42,7 +45,7 @@ class Logs
|
||||
|
||||
public function getFiles(): array
|
||||
{
|
||||
$files = new \FilesystemIterator($this->getFolder());
|
||||
$files = new FilesystemIterator($this->getFolder());
|
||||
$output = [];
|
||||
foreach ($files as $file) {
|
||||
if ($file->isDir()) {
|
||||
@ -57,11 +60,12 @@ class Logs
|
||||
$map = [
|
||||
Parsers\Access::class => '/(access.log)/',
|
||||
Parsers\Error::class => '/(error.log)/',
|
||||
Parsers\Debug::class => '/(debug.log)/',
|
||||
Parsers\Monolog::class => '/(-\d{4}-\d{2}-\d{2}.log)/',
|
||||
Parsers\PHPDefault::class => '/(php_errors.log)/'
|
||||
Parsers\PHPDefault::class => '/(php_errors.log)/',
|
||||
];
|
||||
foreach ($map as $class => $regex) {
|
||||
if (\Safe\preg_match($regex, $filename) === 1) {
|
||||
if (preg_match($regex, $filename) === 1) {
|
||||
return new $class;
|
||||
}
|
||||
}
|
||||
|
@ -12,14 +12,60 @@
|
||||
@push('page_styles')
|
||||
<style>
|
||||
body {overflow-y:scroll;}
|
||||
@foreach ($levels as $level => $colors)
|
||||
.{{$level}} {background-color: {{$colors->background}}; color: {{$colors->text}};}
|
||||
@endforeach
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('page_scripts')
|
||||
<script type="text/javascript">
|
||||
const colors = {
|
||||
debug: {
|
||||
color: '#000',
|
||||
background: '#fff'
|
||||
},
|
||||
info: {
|
||||
color: '#fff',
|
||||
background: '#2727e8'
|
||||
},
|
||||
notice: {
|
||||
color: '#fff',
|
||||
background: '#55f'
|
||||
},
|
||||
warning: {
|
||||
color: '#000',
|
||||
background: '#f5f580'
|
||||
},
|
||||
error: {
|
||||
color: '#fff',
|
||||
background: '#464646'
|
||||
},
|
||||
critical: {
|
||||
color: '#fff',
|
||||
background: '#cb0000'
|
||||
},
|
||||
alert: {
|
||||
color: '#fff',
|
||||
background: '#ec6060'
|
||||
},
|
||||
emergency: {
|
||||
color: '#fff',
|
||||
background: '#b95757'
|
||||
},
|
||||
deprecated: {
|
||||
color: '#fff',
|
||||
background: '#ce4000'
|
||||
}
|
||||
}
|
||||
const icons = {
|
||||
debug: 'bug',
|
||||
info: 'search',
|
||||
notice: 'exclamation',
|
||||
warning: 'exclamation triangle',
|
||||
error: 'times',
|
||||
critical: 'bug',
|
||||
alert: 'bell',
|
||||
emergency: 'ambulance',
|
||||
deprecated: 'virus slash'
|
||||
}
|
||||
const logs = {
|
||||
id: '',
|
||||
start: 0,
|
||||
@ -39,7 +85,7 @@
|
||||
if (this.total > 0 && this.remaining <= 0) {
|
||||
return
|
||||
}
|
||||
$.ajax({
|
||||
return $.ajax({
|
||||
url: '{{$urls->base}}/log/{{urlencode($log->getFilename())}}/more/' + start + '/' + amount
|
||||
}).then(response => {
|
||||
if (response.logs) {
|
||||
@ -48,7 +94,6 @@
|
||||
}
|
||||
this.remaining -= response.logs.length
|
||||
this.start += response.logs.length
|
||||
console.debug(this.total, this.remaining)
|
||||
this.draw().more(response.logs)
|
||||
}
|
||||
})
|
||||
@ -66,6 +111,7 @@
|
||||
}
|
||||
this.draw().unparsed(parent, log)
|
||||
})
|
||||
this.draw().markLast(parent)
|
||||
},
|
||||
title: () => {
|
||||
return $('<div></div>').addClass('title')
|
||||
@ -90,7 +136,7 @@
|
||||
$('<div></div>').addClass('content').append(
|
||||
$('<div></div>').addClass('header').append(
|
||||
$('<span></span>').addClass(log.severity.toLowerCase()).css('padding', '1ex 1em').append(
|
||||
$('<i></i>').addClass('bug icon')
|
||||
$('<i></i>').addClass(icons[(log.severity.toLowerCase())] + ' icon')
|
||||
).append(
|
||||
((log.channel === '') ? '' : log.channel + '.') + log.severity
|
||||
)
|
||||
@ -125,17 +171,38 @@
|
||||
}
|
||||
content.append(card)
|
||||
parent.append(title).append(content)
|
||||
},
|
||||
markLast: parent => {
|
||||
const children = parent.children('.title')
|
||||
children.removeClass('watch')
|
||||
$(children[children.length - 1]).addClass('watch')
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: function() {
|
||||
return {
|
||||
more: payload => {
|
||||
if (payload[0].isIntersecting) {
|
||||
if (payload[0].rootBounds.bottom !== this.watch_pos) {
|
||||
this.get().more(this.start, this.amount)
|
||||
rewatch: observer => {
|
||||
const watch = document.querySelector('.watch')
|
||||
observer.observe(watch)
|
||||
},
|
||||
more: (entries, observer) => {
|
||||
entries.forEach(payload => {
|
||||
if (!$(payload.target).hasClass('watch')) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (payload.intersectionRatio > 0) {
|
||||
if (payload.rootBounds.bottom !== this.watch_pos) {
|
||||
try {
|
||||
this.get().more(this.start, this.amount).then(response => {
|
||||
this.watch().rewatch(observer)
|
||||
})
|
||||
} catch (e) {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -143,12 +210,25 @@
|
||||
this.id = id
|
||||
$(this.get().id()).accordion()
|
||||
|
||||
const ob = new IntersectionObserver(this.watch().more)
|
||||
const watch = document.querySelector('#watch')
|
||||
ob.observe(watch)
|
||||
this.get().more(this.start, this.amount).then(response => {
|
||||
const ob = new IntersectionObserver(this.watch().more)
|
||||
this.watch().rewatch(ob)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function buildColors() {
|
||||
const styleDOM = document.createElement('style')
|
||||
const styles = []
|
||||
Object.keys(colors).forEach(level => {
|
||||
styles.push('.' + level + ' {color: ' + colors[level].color + '; background-color: ' + colors[level].background + '}')
|
||||
})
|
||||
styleDOM.innerHTML = styles.join("\n")
|
||||
document.getElementsByTagName('head')[0].appendChild(styleDOM)
|
||||
}
|
||||
$(document).ready(() => {
|
||||
buildColors()
|
||||
logs.setup('logs')
|
||||
})
|
||||
</script>
|
||||
|
14
app/src/Exception/Parse/EmptyException.php
Normal file
14
app/src/Exception/Parse/EmptyException.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Exception\Parse;
|
||||
|
||||
use Exception;
|
||||
|
||||
class EmptyException extends Exception
|
||||
{
|
||||
public function __construct(string $filename, int $line_number, ?Throwable $previous = null)
|
||||
{
|
||||
$message = "No content in {$filename} line {$line_number}";
|
||||
$code = '1001';
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
14
app/src/Exception/Parse/EmptyLineException.php
Normal file
14
app/src/Exception/Parse/EmptyLineException.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Exception\Parse;
|
||||
|
||||
use Exception;
|
||||
|
||||
class EmptyLineException extends Exception
|
||||
{
|
||||
public function __construct(string $filename, int $line_number, ?Throwable $previous = null)
|
||||
{
|
||||
$message = "Empty line in {$filename} line {$line_number}";
|
||||
$code = 1002;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
@ -3,8 +3,10 @@ namespace ProVM\Logview;
|
||||
|
||||
use DateTimeInterface;
|
||||
use DateTimeImmutable;
|
||||
use JsonSerializable;
|
||||
use ProVM\Common\Define\Log as Definition;
|
||||
|
||||
class Log implements \ProVM\Common\Define\Log, \JsonSerializable
|
||||
class Log implements Definition, JsonSerializable
|
||||
{
|
||||
public function __construct(?string $original = null)
|
||||
{
|
||||
@ -110,15 +112,6 @@ class Log implements \ProVM\Common\Define\Log, \JsonSerializable
|
||||
return isset($this->context) and $this->context !== '';
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return self::COLORS[strtoupper($this->getSeverity())];
|
||||
}
|
||||
public function getBackgroundColor(): string
|
||||
{
|
||||
return self::BACKGROUNDS[strtoupper($this->getSeverity())];
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return ($this->parsed()) ? [
|
||||
@ -147,26 +140,4 @@ class Log implements \ProVM\Common\Define\Log, \JsonSerializable
|
||||
'EMERGENCY',
|
||||
'DEPRECATED',
|
||||
];
|
||||
const COLORS = [
|
||||
'DEBUG' => '#000',
|
||||
'INFO' => '#fff',
|
||||
'NOTICE' => '#fff',
|
||||
'WARNING' => '#000',
|
||||
'ERROR' => '#fff',
|
||||
'CRITICAL' => '#fff',
|
||||
'ALERT' => '#fff',
|
||||
'EMERGENCY' => '#fff',
|
||||
'DEPRECATED' => '#fff',
|
||||
];
|
||||
const BACKGROUNDS = [
|
||||
'DEBUG' => '#fff',
|
||||
'INFO' => '#00f',
|
||||
'NOTICE' => '#55f',
|
||||
'WARNING' => '#dd5',
|
||||
'ERROR' => '#555',
|
||||
'CRITICAL' => '#f00',
|
||||
'ALERT' => '#f55',
|
||||
'EMERGENCY' => '#f55',
|
||||
'DEPRECATED' => '#f50',
|
||||
];
|
||||
}
|
||||
|
@ -3,9 +3,13 @@ namespace ProVM\Logview\Log;
|
||||
|
||||
use Generator;
|
||||
use DateTimeInterface;
|
||||
use ProVM\Logview\Exception\Parse\EmptyException;
|
||||
use ProVM\Logview\Exception\Parse\EmptyLineException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ProVM\Common\Define\Parser;
|
||||
|
||||
use function Safe\{fopen, fclose};
|
||||
|
||||
class File
|
||||
{
|
||||
protected LoggerInterface $logger;
|
||||
@ -77,26 +81,24 @@ class File
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
$cnt = 1;
|
||||
$fh = \Safe\fopen($this->getFullname(), 'r');
|
||||
$debug = compact('i', 'f');
|
||||
$cnt = $i;
|
||||
$fh = fopen($this->getFullname(), 'r');
|
||||
$debug []= ftell($fh);
|
||||
$this->getParser()->advance($fh, $i);
|
||||
$debug []= ftell($fh);
|
||||
\Safe\error_log(var_export($debug,true).PHP_EOL,3,'/logs/debug');
|
||||
while (!feof($fh)) {
|
||||
$line = fgets($fh);
|
||||
if ($cnt < $i) {
|
||||
$cnt ++;
|
||||
try {
|
||||
yield $this->getParser()->parse($fh);
|
||||
} catch (EmptyException | EmptyLineException $e) {
|
||||
continue;
|
||||
}
|
||||
if (!$line) {
|
||||
continue;
|
||||
}
|
||||
if (trim($line) === '') {
|
||||
continue;
|
||||
}
|
||||
yield $this->getParser()->parse(trim($line));
|
||||
$cnt ++;
|
||||
if ($cnt > $f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
\Safe\fclose($fh);
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,25 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Parser;
|
||||
|
||||
use Exception;
|
||||
use Safe\DateTimeImmutable;
|
||||
use Safe\Exceptions\DatetimeException;
|
||||
use ProVM\Common\Define\Log;
|
||||
use ProVM\Common\Implement\Parser;
|
||||
use Safe\Exceptions\DatetimeException;
|
||||
use function Safe\{json_encode, preg_match};
|
||||
|
||||
class Access extends Parser
|
||||
{
|
||||
public function parse(string $content): Log
|
||||
public function parse(mixed &$file_handler): Log
|
||||
{
|
||||
$log = parent::parse($content);
|
||||
$log = parent::parse($file_handler);
|
||||
$content = $log->getOriginal();
|
||||
$regex = "/(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[(?<date>\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2} [\+|\-]\d{4})\] (?<message>.*)/";
|
||||
preg_match($regex, $content, $matches);
|
||||
try {
|
||||
preg_match($regex, $content, $matches);
|
||||
} catch (Exception $e) {
|
||||
return $log;
|
||||
}
|
||||
try {
|
||||
$log->setDate(DateTimeImmutable::createFromFormat('d/M/Y:H:i:s P', $matches['date']));
|
||||
} catch (DatetimeException $e) {
|
||||
|
104
app/src/Parser/Debug.php
Normal file
104
app/src/Parser/Debug.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Parser;
|
||||
|
||||
use Exception;
|
||||
use ProVM\Common\Define\Log;
|
||||
use ProVM\Common\Implement\Parser;
|
||||
|
||||
use function Safe\{error_log, fclose, fopen, preg_match};
|
||||
|
||||
class Debug extends Parser
|
||||
{
|
||||
public function isMultiline(string $filename): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function total(string $filename): int
|
||||
{
|
||||
try {
|
||||
$fh = fopen($filename, 'r');
|
||||
$cnt = 0;
|
||||
while(!feof($fh)) {
|
||||
$line = fgets($fh);
|
||||
if (str_starts_with($line, 'Error')) {
|
||||
$cnt ++;
|
||||
}
|
||||
}
|
||||
fclose($fh);
|
||||
return parent::total($filename);
|
||||
} catch (Exception $e) {
|
||||
error_log($e . PHP_EOL, 3, '/logs/total.log');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function parse(mixed &$file_handler): Log
|
||||
{
|
||||
$log = parent::parse($file_handler);
|
||||
$content = [$log->getOriginal()];
|
||||
$this->parseError($log, $content[0]);
|
||||
while(!feof($file_handler)) {
|
||||
$line_number = ftell($file_handler);
|
||||
$line = fgets($file_handler);
|
||||
if (!$line or trim($line) === '') {
|
||||
continue;
|
||||
}
|
||||
if (str_contains($line, 'Error')) {
|
||||
fseek($file_handler, $line_number);
|
||||
break;
|
||||
}
|
||||
$content []= $line;
|
||||
if (str_starts_with($line, '#')) {
|
||||
$this->parseStack($log, $line);
|
||||
continue;
|
||||
}
|
||||
$this->parseLine($log, $line);
|
||||
}
|
||||
$log->setOriginal(implode(PHP_EOL, $content));
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function advance(mixed &$file_handler, int $offset): void
|
||||
{
|
||||
if ($offset === 0) {
|
||||
return;
|
||||
}
|
||||
$cnt = 0;
|
||||
while(!feof($file_handler)) {
|
||||
$line = fgets($file_handler);
|
||||
if (str_starts_with($line, 'Error')) {
|
||||
$cnt ++;
|
||||
}
|
||||
if ($cnt >= $offset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseError(Log &$log, string $line): void
|
||||
{
|
||||
$regex = '/(?<level>Error): (?<message>\w|\s|\"|\\*)(?<filename>\/\w\.*):(?<line>\d*)/';
|
||||
try {
|
||||
preg_match($regex, $line, $matches);
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
$log->setSeverity($matches['level']);
|
||||
$log->setChannel($matches['level']);
|
||||
$log->setMessage("{$matches['message']} {$matches['filename']}:{$matches['line']}");
|
||||
$log->setContext("{$matches['filename']} ({$matches['line']})");
|
||||
}
|
||||
protected function parseStack(Log &$log, string $line): void
|
||||
{
|
||||
$stack = $log->getStack();
|
||||
$stack []= $line;
|
||||
$log->setStack($stack);
|
||||
}
|
||||
protected function parseLine(Log &$log, string $line): void
|
||||
{
|
||||
$extra = explode(PHP_EOL, $log->getExtra()) ?? [];
|
||||
$extra []= $line;
|
||||
$log->setExtra(implode(PHP_EOL, $extra));
|
||||
}
|
||||
}
|
@ -1,32 +1,57 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Parser;
|
||||
|
||||
use Exception;
|
||||
use Error;
|
||||
use Safe\DateTimeImmutable;
|
||||
use ProVM\Common\Define\Log;
|
||||
use ProVM\Common\Implement\Parser;
|
||||
|
||||
use function Safe\{fopen, fclose, error_log, json_encode, preg_match, preg_match_all, filesize};
|
||||
|
||||
class Monolog extends Parser
|
||||
{
|
||||
public function isMultiline(string $filename): bool
|
||||
{
|
||||
$multiline = new Monolog\Multiline();
|
||||
if ($multiline->total($filename) !== $this->getLines($filename)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function total(string $filename): int
|
||||
{
|
||||
if ($this->isMultiline($filename)) {
|
||||
return (new Monolog\Multiline())->total($filename);
|
||||
}
|
||||
try {
|
||||
$regex = "/\[(?P<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}(?:\+|-)\d{2}:\d{2})\]/";
|
||||
$fh = \Safe\fopen($filename, 'r');
|
||||
$fh = fopen($filename, 'r');
|
||||
$sum = 0;
|
||||
while(!feof($fh)) {
|
||||
$line = fgets($fh);
|
||||
$sum += \Safe\preg_match_all($regex, $line);
|
||||
$sum += preg_match_all($regex, $line);
|
||||
}
|
||||
fclose($fh);
|
||||
return $sum;
|
||||
} catch (\Exception $e) {
|
||||
\Safe\error_log($e . PHP_EOL, 3, '/logs/total.log');
|
||||
} catch (Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public function parse(string $content): Log
|
||||
public function parse(mixed &$file_handler): Log
|
||||
{
|
||||
$log = parent::parse($content);
|
||||
$reset_line = ftell($file_handler);
|
||||
$log = parent::parse($file_handler);
|
||||
$content = $log->getOriginal();
|
||||
|
||||
$line_number = ftell($file_handler);
|
||||
$line = fgets($file_handler);
|
||||
fseek($file_handler, $line_number);
|
||||
if (str_starts_with($line, 'Stack trace')) {
|
||||
fseek($file_handler, $reset_line);
|
||||
return (new Monolog\Multiline())->parse($file_handler);
|
||||
}
|
||||
|
||||
$regex = [
|
||||
"\[(?P<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}(?:\+|-)\d{2}:\d{2})\]",
|
||||
@ -38,18 +63,16 @@ class Monolog extends Parser
|
||||
];
|
||||
$regex = implode('', $regex);
|
||||
try {
|
||||
\Safe\preg_match("/{$regex}/", $content, $matches);
|
||||
} catch (\Exception $e) {
|
||||
\Safe\error_log($content . PHP_EOL, 3, '/logs/debug.log');
|
||||
\Safe\error_log($e . PHP_EOL, 3, '/logs/debug.log');
|
||||
return $log;
|
||||
preg_match("/{$regex}/", $content, $matches);
|
||||
} catch (Exception $e) {
|
||||
return (new Monolog\Multiline())->parse($file_handler);
|
||||
}
|
||||
|
||||
try {
|
||||
$extra = [];
|
||||
try {
|
||||
$log->setDate(DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $matches['date']));
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$log->setDate(new DateTimeImmutable());
|
||||
$extra['date'] = $matches['date'];
|
||||
}
|
||||
@ -72,13 +95,34 @@ class Monolog extends Parser
|
||||
$extra['extra'] = "{{$matches['extra']}}";
|
||||
}
|
||||
if (count($extra) > 0) {
|
||||
$log->setExtra(\Safe\json_encode($extra, JSON_UNESCAPED_SLASHES));
|
||||
$log->setExtra(json_encode($extra, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
} catch (\Error $e) {
|
||||
\Safe\error_log($e . PHP_EOL, 3, '/logs/debug.log');
|
||||
\Safe\error_log(var_export($matches, true) . PHP_EOL, 3, '/logs/debug.log');
|
||||
} catch (Error $e) {
|
||||
error_log($e . PHP_EOL, 3, '/logs/debug.log');
|
||||
error_log(var_export($matches, true) . PHP_EOL, 3, '/logs/debug.log');
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function advance(mixed &$file_handler, int $offset): void
|
||||
{
|
||||
(new Monolog\Multiline())->advance($file_handler, $offset);
|
||||
}
|
||||
|
||||
protected int $lines;
|
||||
protected function getLines(string $filename): int
|
||||
{
|
||||
if (!isset($this->lines)) {
|
||||
$cnt = 0;
|
||||
$fh = fopen($filename, 'r');
|
||||
while(!feof($fh)) {
|
||||
fgets($fh);
|
||||
$cnt ++;
|
||||
}
|
||||
fclose($fh);
|
||||
$this->lines = $cnt;
|
||||
}
|
||||
return $this->lines;
|
||||
}
|
||||
}
|
||||
|
145
app/src/Parser/Monolog/Multiline.php
Normal file
145
app/src/Parser/Monolog/Multiline.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Parser\Monolog;
|
||||
|
||||
use Exception;
|
||||
use Error;
|
||||
use Safe\DateTimeImmutable;
|
||||
use ProVM\Common\Define\Log;
|
||||
use ProVM\Common\Implement\Parser;
|
||||
|
||||
use function Safe\{error_log, fclose, fopen, json_encode, preg_match};
|
||||
|
||||
class Multiline extends Parser
|
||||
{
|
||||
public function isMultiline(string $filename): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function total(string $filename): int
|
||||
{
|
||||
try {
|
||||
$fh = fopen($filename, 'r');
|
||||
$cnt = 0;
|
||||
while(!feof($fh)) {
|
||||
$line = fgets($fh);
|
||||
if (str_starts_with($line, '[')) {
|
||||
$cnt ++;
|
||||
}
|
||||
}
|
||||
fclose($fh);
|
||||
return parent::total($filename);
|
||||
} catch (Exception $e) {
|
||||
error_log($e . PHP_EOL, 3, '/logs/total.log');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function parse(mixed &$file_handler): Log
|
||||
{
|
||||
$log = parent::parse($file_handler);
|
||||
$content = [$log->getOriginal()];
|
||||
$this->parseError($log, $content[0]);
|
||||
while(!feof($file_handler)) {
|
||||
$line_number = ftell($file_handler);
|
||||
$line = fgets($file_handler);
|
||||
if (!$line or trim($line) === '') {
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($line, '[')) {
|
||||
fseek($file_handler, $line_number);
|
||||
break;
|
||||
}
|
||||
$content []= $line;
|
||||
if (str_starts_with($line, 'Stack trace')) {
|
||||
$log->setStack([$line]);
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($line, '#')) {
|
||||
$this->parseStack($log, $line);
|
||||
continue;
|
||||
}
|
||||
$this->parseLine($log, $line);
|
||||
}
|
||||
$log->setOriginal(implode(PHP_EOL, $content));
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function advance(mixed &$file_handler, int $offset): void
|
||||
{
|
||||
if ($offset === 0) {
|
||||
return;
|
||||
}
|
||||
$cnt = 0;
|
||||
while(!feof($file_handler)) {
|
||||
if ($cnt >= $offset) {
|
||||
break;
|
||||
}
|
||||
$line = fgets($file_handler);
|
||||
if (str_starts_with($line, '[')) {
|
||||
$cnt ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseError(Log &$log, string $line): void
|
||||
{
|
||||
$regex = [
|
||||
"\[(?P<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}(?:\+|-)\d{2}:\d{2})\]",
|
||||
"\s(?<channel>\w*)",
|
||||
"\.(?<severity>\w*)",
|
||||
":\s(?<message>.*)",
|
||||
];
|
||||
$regex = implode('', $regex);
|
||||
try {
|
||||
preg_match("/{$regex}/", $line, $matches);
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$extra = [];
|
||||
try {
|
||||
$log->setDate(DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $matches['date']));
|
||||
} catch (Exception $e) {
|
||||
$log->setDate(new DateTimeImmutable());
|
||||
$extra['date'] = $matches['date'];
|
||||
}
|
||||
$log->setChannel($matches['channel']);
|
||||
$log->setSeverity($matches['severity']);
|
||||
$message = $matches['message'];
|
||||
if (str_contains($message, 'Stack trace')) {
|
||||
list($msg, $data) = explode('Stack trace:', $message);
|
||||
$message = trim($msg);
|
||||
$regex = '/\s#\d+\s/';
|
||||
$lines = preg_split($regex, $data);
|
||||
array_shift($lines);
|
||||
$log->setStack($lines);
|
||||
}
|
||||
$log->setMessage($message);
|
||||
if ($matches['context'] !== '') {
|
||||
$log->setContext("{{$matches['context']}}");
|
||||
}
|
||||
if (isset($matches['extra']) and $matches['extra'] !== '') {
|
||||
$extra['extra'] = "{{$matches['extra']}}";
|
||||
}
|
||||
if (count($extra) > 0) {
|
||||
$log->setExtra(json_encode($extra, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
} catch (Error $e) {
|
||||
error_log($e . PHP_EOL, 3, '/logs/debug.log');
|
||||
error_log(var_export($matches, true) . PHP_EOL, 3, '/logs/debug.log');
|
||||
}
|
||||
}
|
||||
protected function parseStack(Log &$log, string $line): void
|
||||
{
|
||||
$stack = $log->getStack();
|
||||
$stack []= $line;
|
||||
$log->setStack($stack);
|
||||
}
|
||||
protected function parseLine(Log &$log, string $line): void
|
||||
{
|
||||
$extra = explode(PHP_EOL, $log->getExtra()) ?? [];
|
||||
$extra []= $line;
|
||||
$log->setExtra(implode(PHP_EOL, $extra));
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
<?php
|
||||
namespace ProVM\Logview\Parser;
|
||||
|
||||
use Error;
|
||||
use Exception;
|
||||
use Safe\DateTimeImmutable;
|
||||
use function Safe\{fopen, fclose, preg_match_all, preg_match, error_log, json_encode};
|
||||
use ProVM\Common\Define\Log;
|
||||
use ProVM\Common\Implement\Parser;
|
||||
|
||||
@ -11,42 +14,43 @@ class PHPDefault extends Parser
|
||||
{
|
||||
try {
|
||||
$regex = "/\[(?<date>\d{2}-\w{3}-\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})\]/";
|
||||
$fh = \Safe\fopen($filename, 'r');
|
||||
$fh = fopen($filename, 'r');
|
||||
$sum = 0;
|
||||
while(!feof($fh)) {
|
||||
$line = fgets($fh);
|
||||
$sum += \Safe\preg_match_all($regex, $line);
|
||||
$sum += preg_match_all($regex, $line);
|
||||
}
|
||||
fclose($fh);
|
||||
return $sum;
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public function parse(string $content): Log
|
||||
public function parse(mixed &$file_handler): Log
|
||||
{
|
||||
$log = parent::parse($content);
|
||||
$log = parent::parse($file_handler);
|
||||
$content = $log->getOriginal();
|
||||
$regex = "/\[(?<date>\d{2}-\w{3}-\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})\]\s(?<level>PHP|User)\s(?<severity>\w+):\s(?<message>.*)/";
|
||||
try {
|
||||
\Safe\preg_match($regex, $content, $matches);
|
||||
} catch (\Error $e) {
|
||||
\Safe\error_log($e . PHP_EOL, 3, '/logs/debug.log');
|
||||
preg_match($regex, $content, $matches);
|
||||
} catch (Error $e) {
|
||||
error_log($e . PHP_EOL, 3, '/logs/debug.log');
|
||||
return $log;
|
||||
}
|
||||
|
||||
$extra = [];
|
||||
try {
|
||||
$log->setDate(DateTimeImmutable::createFromFormat('d-M-Y H:i:s e', $matches['date']));
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$log->setDate(new DateTimeImmutable());
|
||||
$extra['date'] = $matches['date'];
|
||||
}
|
||||
$log->setChannel('');
|
||||
$log->setSeverity($matches['severity']);
|
||||
$log->setMessage($matches['message']);
|
||||
$log->setContext(\Safe\json_encode(['level' => $matches['level']], JSON_UNESCAPED_SLASHES));
|
||||
$log->setContext(json_encode(['level' => $matches['level']], JSON_UNESCAPED_SLASHES));
|
||||
if (count($extra) > 0) {
|
||||
$log->setExtra(\Safe\json_encode($extra, JSON_UNESCAPED_SLASHES));
|
||||
$log->setExtra(json_encode($extra, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
return $log;
|
||||
|
Reference in New Issue
Block a user