Skip to content

Commit

Permalink
Improved user experience
Browse files Browse the repository at this point in the history
  • Loading branch information
fkeloks committed Sep 2, 2022
1 parent 5e69b6a commit 1ead843
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 342 deletions.
File renamed without changes.
120 changes: 54 additions & 66 deletions src/Controller/LogsManagerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@

use eZ\Publish\Core\MVC\Symfony\Security\Authorization\Attribute;
use IbexaLogsUi\Bundle\LogManager\LogFile;
use IbexaLogsUi\Bundle\LogManager\LogTrunkCache;
use IbexaLogsUi\Bundle\LogManager\LogsCache;
use EzSystems\EzPlatformAdminUiBundle\Controller\Controller;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\HttpFoundation\Response;

class LogsManagerController extends Controller
{
/** @var int */
public static $PER_PAGE_LOGS = 200;

/** @var int */
public static $MAX_LOGS = 10000;
public const PER_PAGE_LOGS = 200;
public const MAX_LOGS = 3000;

/** @var string */
private $kernelCacheDir;
Expand All @@ -31,65 +27,56 @@ public function __construct(string $kernelCacheDir, Logger $monologLogger)
$this->monologLogger = $monologLogger;
}

public function index(int $chunkId = 1): Response
public function index(string $level = 'all', int $page = 1): Response
{
if ($level === 'reload') {
return $this->reload();
}

$this->denyAccessUnlessGranted(new Attribute('ibexa_logs_ui', 'view'));

$logPaths = $this->getLogPaths();
$logPath = reset($logPaths);

if (!is_string($logPath) || !file_exists($logPath)) {
return $this->render('@ezdesign/logs/logs.html.twig', [
'logPath' => $logPath,
'currentChunkId' => $chunkId,
'perPageLogs' => self::$PER_PAGE_LOGS,
'total' => null,
'logs' => []
]);
return $this->renderLogs($logPath, $level, $page);
}

$logFile = new LogFile($logPath);
$logTrunkCache = new LogTrunkCache($logPath, $this->kernelCacheDir, 'ibexa_logs_ui');
$logsCache = new LogsCache($logPath, $this->kernelCacheDir);

/** @var CacheItem $totalCacheItem */
$totalCacheItem = $logTrunkCache->getCacheSystem()->getItem($logTrunkCache->getCacheKey('total'));
$total = $totalCacheItem->isHit() ? $totalCacheItem->get() : 0;

if ($chunkId >= 2 && $chunkId > ceil($total / self::$PER_PAGE_LOGS)) {
$chunkId = 1;
}
$logs = $logsCache->get(static function () use ($logFile) {
$lines = $logFile->tail(self::MAX_LOGS);

if (!$logTrunkCache->hasChunk($chunkId)) {
$lines = $logFile->tail(self::$MAX_LOGS);
return $logFile->parse($lines);
});

if (!empty($lines)) {
$total = count($lines);
$logTrunkCache->getCacheSystem()->save($totalCacheItem->set($total)->expiresAfter(300));
// Sort available levels
$logLevels = array_unique(array_column($logs, 'level'));
$logLevels = array_intersect(array_keys(LogFile::LOG_LEVELS), $logLevels);

foreach (array_chunk($lines, self::$PER_PAGE_LOGS) as $index => $chunk) {
$logTrunkCache->setChunk($index + 1, $chunk);
}
// Filter by level
if ($level !== 'all') {
$filter = mb_strtoupper($level);
$logs = array_filter($logs, static function (array $log) use ($filter) {
return $log['level'] === $filter;
});
}

$logs = array_slice($logFile->parse($lines), 0, self::$PER_PAGE_LOGS);
}
} else {
$lines = $logTrunkCache->getChunk($chunkId);
$logs = $logFile->parse($lines);
// Empty logs for current level
if (empty($logs)) {
return $this->renderLogs($logPath, $level, $page);
}

$logs = array_reduce($logs ?? [], static function (array $carry, array $log) {
$carry[$log['level']][] = $log;
$total = count($logs);
$logs = array_chunk($logs, 200);

return $carry;
}, []);
// Invalid page number
if (!array_key_exists($page - 1, $logs)) {
return $this->renderLogs($logPath, $level, $page, $total, $logs[0], $logLevels);
}

return $this->render('@ezdesign/logs/logs.html.twig', [
'logPath' => $logPath,
'currentChunkId' => $chunkId,
'perPageLogs' => self::$PER_PAGE_LOGS,
'total' => $total ?? 0,
'logs' => $logs
]);
return $this->renderLogs($logPath, $level, $page, $total, $logs[$page - 1], $logLevels);
}

public function reload(): Response
Expand All @@ -100,25 +87,8 @@ public function reload(): Response
$logPath = reset($logPaths);

if (is_string($logPath) && file_exists($logPath)) {
$logFile = new LogFile($logPath);
$logTrunkCache = new LogTrunkCache($logPath, $this->kernelCacheDir, 'ibexa_logs_ui');

$lines = $logFile->tail(self::$MAX_LOGS);

if (!empty($lines)) {
/** @var CacheItem $oldTotalCacheItem */
$oldTotalCacheItem = $logTrunkCache->getCacheSystem()->getItem($logTrunkCache->getCacheKey('total'));
if ($oldTotalCacheItem->isHit() && $oldTotalCacheItem->get()) {
$logTrunkCache->clearChunks($oldTotalCacheItem->get());
}

$total = count($lines);
$logTrunkCache->getCacheSystem()->save($oldTotalCacheItem->set($total)->expiresAfter(300));

foreach (array_chunk($lines, self::$PER_PAGE_LOGS) as $index => $chunk) {
$logTrunkCache->setChunk($index + 1, $chunk);
}
}
$logsCache = new LogsCache($logPath, $this->kernelCacheDir);
$logsCache->clear();
}

return $this->redirectToRoute('ibexa_logs_ui_index');
Expand All @@ -137,4 +107,22 @@ private function getLogPaths(): array
);
}));
}

private function renderLogs(
string $logPath,
string $level,
int $page,
int $total = 0,
array $logs = [],
array $logLevels = LogFile::LOG_LEVELS
): Response {
return $this->render('@ezdesign/logs/logs.html.twig', [
'log_path' => $logPath,
'level' => $level,
'page' => $page,
'total' => $total,
'logs' => $logs,
'log_levels' => $logLevels
]);
}
}
4 changes: 2 additions & 2 deletions src/LogManager/LogFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class LogFile
{
/** @var array Log levels for Bootstrap classes */
private const LOG_LEVELS = [
public const LOG_LEVELS = [
'DEBUG' => 'secondary',
'INFO' => 'info',
'NOTICE' => 'info',
Expand Down Expand Up @@ -65,7 +65,7 @@ public function tail(int $lines = 100, bool $skipEmptyLines = true): array
$line = fgets($handle);
if (trim($line)) {
$text[$lines - $lineCounter - 1] = $line;
} elseif ($skipEmptyLines && $lineCounter < ($lines + LogsManagerController::$PER_PAGE_LOGS)) {
} elseif ($skipEmptyLines && $lineCounter < ($lines + LogsManagerController::PER_PAGE_LOGS)) {
$lineCounter++;
}

Expand Down
117 changes: 0 additions & 117 deletions src/LogManager/LogTrunkCache.php

This file was deleted.

51 changes: 51 additions & 0 deletions src/LogManager/LogsCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace IbexaLogsUi\Bundle\LogManager;

use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\CacheItem;

class LogsCache
{
/** @var FilesystemAdapter */
private $cacheSystem;

/** @var string */
private $logPath;

public function __construct(string $logPath, string $cacheDirectory)
{
$this->logPath = $logPath;
$this->cacheSystem = new FilesystemAdapter('ibexa_logs_ui', 0, $cacheDirectory);
}

public function get(callable $setter): array
{
/** @var CacheItem $cacheItem */
$cacheItem = $this->cacheSystem->getItem($this->getCacheKey());

if ($cacheItem->isHit()) {
return $cacheItem->get();
}

$value = $setter();

$cacheItem
->set($value)
->expiresAfter(300);

$this->cacheSystem->save($cacheItem);

return $value;
}

public function clear(): bool
{
return $this->cacheSystem->deleteItem($this->getCacheKey());
}

private function getCacheKey(): string
{
return 'ibexa_logs_ui.' . md5($this->logPath);
}
}
2 changes: 1 addition & 1 deletion src/Parser/LineLogParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class LineLogParser
{
/** @var string */
private const PARSER_PATTERN = '/\[(?<date>.*?)\] (?<logger>\w+).(?<level>\w+): (?<message>[^\[\{]+) (?<context>[\[\{].*[\]\}]) (?<extra>[\[\{].*[\]\}])/';
private const PARSER_PATTERN = '/^\[(?<date>.*?)\] (?<logger>\w+).(?<level>\w+): (?<message>[^\[\{]+) (?<context>[\[\{].*[\]\}]) (?<extra>[\[\{].*[\]\}])$/';

/** @var array */
private const PARSER_GROUPS = ['date', 'logger', 'level', 'message', 'context', 'extra'];
Expand Down
7 changes: 4 additions & 3 deletions src/Resources/config/routing.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
ibexa_logs_ui_index:
path: /ibexa-logs-ui/{chunkId}
defaults: { _controller: IbexaLogsUi\Bundle\Controller\LogsManagerController::index, chunkId: 1 }
path: /ibexa-logs-ui/{level}/{page}
defaults: { _controller: IbexaLogsUi\Bundle\Controller\LogsManagerController::index, level: all, page: 1 }
requirements:
chunkId: '\d+'
level: '\w+'
page: '\d+'

ibexa_logs_ui_reload:
path: /ibexa-logs-ui/reload
Expand Down
5 changes: 4 additions & 1 deletion src/Resources/translations/messages.en.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
logs_ui.menu.label: 'Logs'
logs_ui.text.reload: 'Reload'
logs_ui.text.all_levels: 'All levels'
logs_ui.text.log_path: 'Path:'
logs_ui.text.last_message: 'Last message:'
logs_ui.text.show_more: 'Show more'
logs_ui.text.is_empty: 'The log file seems empty'
logs_ui.text.is_empty: 'The log file seems to be empty for the level searched (%level%).'
logs_ui.text.max_logs: 'For performance reasons, only the last %maxLogs% lines of the file are read.'
logs_ui.text.context: 'Context:'
logs_ui.text.extra: 'Extra:'
logs_ui.text.page: 'Page %page% of %pages%'
logs_ui.text.pagination: 'Display of <strong>%perPage%</strong> lines out of <strong>%total%</strong> in total.'
Loading

0 comments on commit 1ead843

Please sign in to comment.