From 0580cb5d307b63eee1faebce43c899a8bb04e7bb Mon Sep 17 00:00:00 2001 From: Aldarien Date: Thu, 18 May 2023 15:38:06 -0400 Subject: [PATCH 1/5] Changed icons --- app/resources/views/logs/show.blade.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/resources/views/logs/show.blade.php b/app/resources/views/logs/show.blade.php index aedf07d..c886328 100644 --- a/app/resources/views/logs/show.blade.php +++ b/app/resources/views/logs/show.blade.php @@ -20,6 +20,17 @@ @push('page_scripts') diff --git a/app/src/Log.php b/app/src/Log.php index 5ea50f2..360a98d 100644 --- a/app/src/Log.php +++ b/app/src/Log.php @@ -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', - ]; } diff --git a/app/src/Parser/Monolog.php b/app/src/Parser/Monolog.php index c35d2ca..460f209 100644 --- a/app/src/Parser/Monolog.php +++ b/app/src/Parser/Monolog.php @@ -1,7 +1,10 @@ \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) { + error_log($e . PHP_EOL, 3, '/logs/total.log'); return 0; } } @@ -38,10 +41,10 @@ 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'); + preg_match("/{$regex}/", $content, $matches); + } catch (Exception $e) { + error_log($content . PHP_EOL, 3, '/logs/debug.log'); + error_log($e . PHP_EOL, 3, '/logs/debug.log'); return $log; } @@ -49,7 +52,7 @@ class Monolog extends Parser $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,11 +75,11 @@ 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; diff --git a/app/src/Parser/PHPDefault.php b/app/src/Parser/PHPDefault.php index 5a763f5..bed3bc2 100644 --- a/app/src/Parser/PHPDefault.php +++ b/app/src/Parser/PHPDefault.php @@ -1,7 +1,10 @@ \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; } } @@ -28,25 +31,25 @@ class PHPDefault extends Parser $log = parent::parse($content); $regex = "/\[(?\d{2}-\w{3}-\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})\]\s(?PHP|User)\s(?\w+):\s(?.*)/"; 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; From 806d1be9cf6f86b6b780bb1b03b9e203bbc53bca Mon Sep 17 00:00:00 2001 From: Aldarien Date: Fri, 19 May 2023 11:16:50 -0400 Subject: [PATCH 3/5] Added multiline parsing --- app/common/Define/Log.php | 23 +++ app/common/Define/Parser.php | 29 +++- app/common/Implement/Parser.php | 34 +++- app/common/Service/Logs.php | 3 +- app/src/Exception/Parse/EmptyException.php | 14 ++ .../Exception/Parse/EmptyLineException.php | 14 ++ app/src/Log/File.php | 28 ++-- app/src/Parser/Access.php | 15 +- app/src/Parser/Debug.php | 104 +++++++++++++ app/src/Parser/Monolog.php | 55 ++++++- app/src/Parser/Monolog/Multiline.php | 145 ++++++++++++++++++ app/src/Parser/PHPDefault.php | 7 +- 12 files changed, 440 insertions(+), 31 deletions(-) create mode 100644 app/src/Exception/Parse/EmptyException.php create mode 100644 app/src/Exception/Parse/EmptyLineException.php create mode 100644 app/src/Parser/Debug.php create mode 100644 app/src/Parser/Monolog/Multiline.php diff --git a/app/common/Define/Log.php b/app/common/Define/Log.php index 5e59620..3bc1a65 100644 --- a/app/common/Define/Log.php +++ b/app/common/Define/Log.php @@ -1,7 +1,30 @@ = $offset) { + break; + } + } + } } diff --git a/app/common/Service/Logs.php b/app/common/Service/Logs.php index 1d0fa31..4a4b43c 100644 --- a/app/common/Service/Logs.php +++ b/app/common/Service/Logs.php @@ -60,8 +60,9 @@ 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 (preg_match($regex, $filename) === 1) { diff --git a/app/src/Exception/Parse/EmptyException.php b/app/src/Exception/Parse/EmptyException.php new file mode 100644 index 0000000..dd6ddcb --- /dev/null +++ b/app/src/Exception/Parse/EmptyException.php @@ -0,0 +1,14 @@ +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); } } diff --git a/app/src/Parser/Access.php b/app/src/Parser/Access.php index 6c8c057..9914c34 100644 --- a/app/src/Parser/Access.php +++ b/app/src/Parser/Access.php @@ -1,18 +1,25 @@ getOriginal(); $regex = "/(?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[(?\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2} [\+|\-]\d{4})\] (?.*)/"; - 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) { diff --git a/app/src/Parser/Debug.php b/app/src/Parser/Debug.php new file mode 100644 index 0000000..8330b50 --- /dev/null +++ b/app/src/Parser/Debug.php @@ -0,0 +1,104 @@ +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 = '/(?Error): (?\w|\s|\"|\\*)(?\/\w\.*):(?\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)); + } +} diff --git a/app/src/Parser/Monolog.php b/app/src/Parser/Monolog.php index 460f209..9f0a9ab 100644 --- a/app/src/Parser/Monolog.php +++ b/app/src/Parser/Monolog.php @@ -4,14 +4,27 @@ namespace ProVM\Logview\Parser; use Exception; use Error; use Safe\DateTimeImmutable; -use function Safe\{fopen, error_log, json_encode, preg_match, preg_match_all}; 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\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}(?:\+|-)\d{2}:\d{2})\]/"; $fh = fopen($filename, 'r'); @@ -23,13 +36,22 @@ class Monolog extends Parser fclose($fh); return $sum; } catch (Exception $e) { - error_log($e . PHP_EOL, 3, '/logs/total.log'); 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\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}(?:\+|-)\d{2}:\d{2})\]", @@ -43,9 +65,7 @@ class Monolog extends Parser try { preg_match("/{$regex}/", $content, $matches); } catch (Exception $e) { - error_log($content . PHP_EOL, 3, '/logs/debug.log'); - error_log($e . PHP_EOL, 3, '/logs/debug.log'); - return $log; + return (new Monolog\Multiline())->parse($file_handler); } try { @@ -84,4 +104,25 @@ class Monolog extends Parser 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; + } } diff --git a/app/src/Parser/Monolog/Multiline.php b/app/src/Parser/Monolog/Multiline.php new file mode 100644 index 0000000..2026f36 --- /dev/null +++ b/app/src/Parser/Monolog/Multiline.php @@ -0,0 +1,145 @@ +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\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}(?:\+|-)\d{2}:\d{2})\]", + "\s(?\w*)", + "\.(?\w*)", + ":\s(?.*)", + ]; + $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)); + } +} diff --git a/app/src/Parser/PHPDefault.php b/app/src/Parser/PHPDefault.php index bed3bc2..d4d1bf5 100644 --- a/app/src/Parser/PHPDefault.php +++ b/app/src/Parser/PHPDefault.php @@ -4,7 +4,7 @@ namespace ProVM\Logview\Parser; use Error; use Exception; use Safe\DateTimeImmutable; -use function Safe\{fopen, preg_match_all, preg_match, error_log, json_encode}; +use function Safe\{fopen, fclose, preg_match_all, preg_match, error_log, json_encode}; use ProVM\Common\Define\Log; use ProVM\Common\Implement\Parser; @@ -26,9 +26,10 @@ class PHPDefault extends Parser 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 = "/\[(?\d{2}-\w{3}-\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})\]\s(?PHP|User)\s(?\w+):\s(?.*)/"; try { preg_match($regex, $content, $matches); From 4b82650fe8519f95c225340c8ff6012f2c4b4b85 Mon Sep 17 00:00:00 2001 From: Aldarien Date: Fri, 19 May 2023 12:27:17 -0400 Subject: [PATCH 4/5] Fixed observer --- app/resources/views/logs/show.blade.php | 46 ++++++++++++++++++------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/app/resources/views/logs/show.blade.php b/app/resources/views/logs/show.blade.php index 89a29ac..6f5ba6d 100644 --- a/app/resources/views/logs/show.blade.php +++ b/app/resources/views/logs/show.blade.php @@ -12,9 +12,6 @@ @push('page_styles') @endpush @@ -88,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) { @@ -97,7 +94,6 @@ } this.remaining -= response.logs.length this.start += response.logs.length - console.debug(this.total, this.remaining) this.draw().more(response.logs) } }) @@ -115,6 +111,7 @@ } this.draw().unparsed(parent, log) }) + this.draw().markLast(parent) }, title: () => { return $('
').addClass('title') @@ -174,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() + } + } + } + }) + } } }, @@ -192,9 +210,11 @@ 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) + }) + } } From 5a609b14f680ef6340372d703ea71af58809e8c8 Mon Sep 17 00:00:00 2001 From: Aldarien Date: Fri, 19 May 2023 12:46:24 -0400 Subject: [PATCH 5/5] Added coloring to content --- app/resources/views/logs/show.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/resources/views/logs/show.blade.php b/app/resources/views/logs/show.blade.php index 6f5ba6d..52ce08e 100644 --- a/app/resources/views/logs/show.blade.php +++ b/app/resources/views/logs/show.blade.php @@ -120,6 +120,7 @@ unparsed: (parent, log) => { const title = this.draw().title() const content = $('
').addClass('content') + content.addClass(log.severity.toLowerCase()) title.append(log.original) content.html(log.original) parent.append(title).append(content) @@ -127,6 +128,7 @@ parsed: (parent, log) => { const title = this.draw().title() const content = $('
').addClass('content') + content.addClass(log.severity.toLowerCase()) title.append( $('') .addClass(log.severity.toLowerCase()).css('padding', '.5ex 1ex')//.css('padding-left', '1ex').css('padding-right', '1ex')