Skip to content

Commit

Permalink
Add functional tests (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
frankdekker committed Jan 20, 2024
1 parent 7177482 commit 6ddbd70
Show file tree
Hide file tree
Showing 49 changed files with 614 additions and 185 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ jobs:
matrix:
php-versions: [ '8.1', '8.2', '8.3' ]
composer-flags: [ '', '--prefer-lowest' ]
exclude:
- php-versions: 8.3
composer-flags: ''
steps:
- uses: actions/checkout@v4

Expand All @@ -33,7 +30,7 @@ jobs:
run: composer validate

- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-suggest --prefer-stable ${{ matrix.composer-flags }}
run: composer update --prefer-dist --no-progress --prefer-stable ${{ matrix.composer-flags }}

- name: Run test suite
run: composer test
Expand All @@ -51,7 +48,7 @@ jobs:
coverage: pcov

- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-suggest --prefer-stable
run: composer update --prefer-dist --no-progress --prefer-stable

- name: Run test suite
run: php -dpcov.enabled=1 -dpcov.exclude="~vendor~" vendor/bin/phpunit --testsuite unit,integration --coverage-clover ./.coverage/coverage.xml
Expand Down
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/.idea
/vendor
/composer.lock
/phpunit.xml
/.phpunit.result.cache
/composer.lock
/dev/public/bundles
/dev/var
/phpunit.xml
/tests/.kernel
/vendor
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
"require": {
"php": ">=8.1",
"ext-json": "*",
"symfony/asset": "^6.0||^7.0",
"psr/clock": "^1.0",
"symfony/config": "^6.0||^7.0",
"symfony/dependency-injection": "^6.0||^7.0",
"symfony/filesystem": "^6.0||^7.0",
"symfony/finder": "^6.0||^7.0",
"symfony/framework-bundle": "^6.0||^7.0",
"symfony/filesystem": "^6.0||^7.0",
"symfony/http-foundation": "^6.0||^7.0",
"symfony/http-kernel": "^6.0||^7.0",
"symfony/routing": "^6.0||^7.0",
Expand All @@ -58,7 +58,7 @@
"symfony/css-selector": "^6.0||^7.0",
"symfony/monolog-bridge": "^6.0||^7.0",
"symfony/monolog-bundle": "^3.10",
"symfony/phpunit-bridge": "^6.0||^7.0",
"symfony/phpunit-bridge": "^6.4||^7.0",
"symfony/runtime": "^6.0||^7.0",
"symfony/templating": "^6.0||^7.0",
"symfony/twig-bundle": "^6.0||^7.0",
Expand Down
1 change: 1 addition & 0 deletions dev/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ web_profiler:
intercept_redirects: false

twig:
debug: false
strict_variables: true

monolog:
Expand Down
6 changes: 6 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fd_log_viewer:
start_of_line_pattern: '/^\[\d{4}-\d{2}-\d{2}[^]]*]\s+\S+\.\S+:/'
log_message_pattern: '/^\[(?P<date>[^\]]+)\]\s+(?P<channel>[^\.]+)\.(?P<severity>[^:]+):\s+(?P<message>.*)\s+(?P<context>[[{].*?[\]}])\s+(?P<extra>[[{].*?[\]}])\s+$/s'
date_format: "Y-m-d H:i:s"
enable_default_monolog: true
```

## Configuration reference
Expand Down Expand Up @@ -112,3 +113,8 @@ If you use a custom monolog format, adjust this pattern to your needs.
### log_files.date_format <small>`string`</small>

This is the date format that will be used to format the date in frontend. Default: `Y-m-d H:i:s`


### enable_default_monolog <small>`boolean`</small>

Should the default monolog configuration be enabled? Default: `true`
6 changes: 6 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
beStrictAboutTodoAnnotatedTests="true"
executionOrder="defects"
>
<php>
<env name="KERNEL_CLASS" value="FD\LogViewer\Tests\Utility\TestKernel"/>
</php>
<testsuites>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
Expand Down
14 changes: 13 additions & 1 deletion src/Controller/IndexController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
use FD\LogViewer\Entity\Output\DirectionEnum;
use FD\LogViewer\Routing\RouteService;
use FD\LogViewer\Service\Folder\LogFolderOutputProvider;
use FD\LogViewer\Service\JsonManifestAssetLoader;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class IndexController extends AbstractController
{
public function __construct(
private readonly JsonManifestAssetLoader $assetLoader,
private readonly RouteService $routeService,
private readonly LogFolderOutputProvider $folderOutputProvider
) {
Expand All @@ -29,6 +31,16 @@ public function __invoke(): Response
// retrieve all log files and folders
$folders = $this->folderOutputProvider->provide(DirectionEnum::Desc);

return $this->render('@FdLogViewer/index.html.twig', ['base_uri' => $baseUri, 'folders' => $folders]);
return $this->render(
'@FdLogViewer/index.html.twig',
[
'base_uri' => $baseUri,
'folders' => $folders,
'assets' => [
'style' => $this->assetLoader->getUrl('style.css'),
'js' => $this->assetLoader->getUrl('src/main.ts')
],
]
);
}
}
5 changes: 5 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode = $tree->getRootNode();

$rootNode->children()
->scalarNode('enable_default_monolog')
->info('Enable default monolog configuration')
->defaultTrue()
->end()
->arrayNode('log_files')
->info('List of log files to show')
->useAttributeAsKey('log_name')
Expand Down Expand Up @@ -62,6 +66,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end();


return $tree;
}
}
52 changes: 21 additions & 31 deletions src/DependencyInjection/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@

use FD\LogViewer\Entity\Config\FinderConfig;
use FD\LogViewer\Entity\Config\LogFilesConfig;
use FD\LogViewer\Service\JsonManifestVersionStrategy;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Throwable;
Expand All @@ -18,7 +16,7 @@
* @codeCoverageIgnore
* @internal
*/
final class Extension extends BaseExtension implements PrependExtensionInterface
final class Extension extends BaseExtension
{
/**
* @inheritDoc
Expand All @@ -32,19 +30,8 @@ public function load(array $configs, ContainerBuilder $container): void
$mergedConfigs = $this->processConfiguration(new Configuration(), $configs);

// add defaults
if (count($mergedConfigs['log_files']) === 0) {
$mergedConfigs['log_files']['monolog'] = [
'type' => 'monolog',
'name' => 'Monolog',
'finder' => [
'in' => '%kernel.logs_dir%',
'name' => '*.log',
'ignoreUnreadableDirs' => true,
'followLinks' => false
],
'downloadable' => false,
'deletable' => false,
];
if ($mergedConfigs['enable_default_monolog']) {
$mergedConfigs = self::addMonologDefault($mergedConfigs);
}

foreach ($mergedConfigs['log_files'] as $key => $config) {
Expand Down Expand Up @@ -76,22 +63,25 @@ public function getAlias(): string
}

/**
* @inheritdoc
* @template T of array
* @phpstan-param T $configs
*
* @phpstan-return T
*/
public function prepend(ContainerBuilder $container): void
private static function addMonologDefault(array $configs): array
{
$container->prependExtensionConfig(
'framework',
[
'assets' => [
'enabled' => true,
'packages' => [
'fd_symfony_log_viewer' => [
'version_strategy' => JsonManifestVersionStrategy::class
],
],
],
]
);
// monolog
$configs['log_files']['monolog']['type'] ??= 'monolog';
$configs['log_files']['monolog']['name'] ??= 'Monolog';
$configs['log_files']['monolog']['downloadable'] ??= false;
$configs['log_files']['monolog']['deletable'] ??= false;

// finder
$configs['log_files']['monolog']['finder']['in'] ??= '%kernel.logs_dir%';
$configs['log_files']['monolog']['finder']['name'] ??= '*.log';
$configs['log_files']['monolog']['finder']['ignoreUnreadableDirs'] ??= true;
$configs['log_files']['monolog']['finder']['followLinks'] ??= false;

return $configs;
}
}
6 changes: 5 additions & 1 deletion src/Entity/TempFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use RuntimeException;
use SplFileInfo;

use function register_shutdown_function;
use function sys_get_temp_dir;
use function tempnam;
use function unlink;
Expand All @@ -21,9 +22,12 @@ public function __construct()
// @codeCoverageIgnoreEnd
}
parent::__construct($path);

// cleanup temp file when script ends (and not when object lifecycle ends)
register_shutdown_function(fn() => $this->destruct());
}

public function __destruct()
public function destruct(): void
{
if ($this->isFile()) {
@unlink($this->getPathname());
Expand Down
13 changes: 9 additions & 4 deletions src/Iterator/MaxRuntimeIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace FD\LogViewer\Iterator;

use IteratorAggregate;
use Psr\Clock\ClockInterface;
use Traversable;

/**
Expand All @@ -16,8 +17,12 @@ class MaxRuntimeIterator implements IteratorAggregate
/**
* @param Traversable<K, V> $iterator
*/
public function __construct(private readonly Traversable $iterator, private readonly int $maxRuntimeInSeconds, private bool $throw = true)
{
public function __construct(
private readonly ClockInterface $clock,
private readonly Traversable $iterator,
private readonly int $maxRuntimeInSeconds,
private bool $throw = true
) {
}

/**
Expand All @@ -26,11 +31,11 @@ public function __construct(private readonly Traversable $iterator, private read
*/
public function getIterator(): Traversable
{
$startTime = microtime(true);
$startTime = $this->clock->now()->getTimestamp();
foreach ($this->iterator as $key => $value) {
yield $key => $value;

if (microtime(true) - $startTime > $this->maxRuntimeInSeconds) {
if ($this->clock->now()->getTimestamp() - $startTime > $this->maxRuntimeInSeconds) {
if ($this->throw) {
throw new MaxRuntimeException();
}
Expand Down
8 changes: 5 additions & 3 deletions src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
use FD\LogViewer\Service\Folder\LogFolderOutputProvider;
use FD\LogViewer\Service\Folder\LogFolderOutputSorter;
use FD\LogViewer\Service\Folder\ZipArchiveFactory;
use FD\LogViewer\Service\JsonManifestVersionStrategy;
use FD\LogViewer\Service\JsonManifestAssetLoader;
use FD\LogViewer\Service\PerformanceService;
use FD\LogViewer\Service\VersionService;
use FD\LogViewer\StreamReader\StreamReaderFactory;
use FD\LogViewer\Util\Clock;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

use function Symfony\Component\DependencyInjection\Loader\Configurator\inline_service;
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;

return static function (ContainerConfigurator $container): void {
Expand All @@ -50,7 +52,7 @@
$services->set(RouteLoader::class)
->tag('routing.loader');

$services->set(JsonManifestVersionStrategy::class)
$services->set(JsonManifestAssetLoader::class)
->arg('$manifestPath', '%kernel.project_dir%/public/bundles/fdlogviewer/.vite/manifest.json');

$services->set(FinderFactory::class);
Expand All @@ -60,7 +62,7 @@
$services->set(LogFolderOutputProvider::class);
$services->set(LogFolderOutputSorter::class);
$services->set(LogRecordsOutputProvider::class);
$services->set(LogParser::class);
$services->set(LogParser::class)->arg('$clock', inline_service(Clock::class));
$services->set(LogFileParserProvider::class)
->arg('$logParsers', tagged_iterator('fd.symfony.log.viewer.monolog_file_parser', 'name'));
$services->set(LogQueryDtoFactory::class);
Expand Down
6 changes: 3 additions & 3 deletions src/Resources/views/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
<meta charset="UTF-8">
<link rel="icon"
type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M15 3.604H1v1.891h14v-1.89ZM1 7.208V16l7-3.926L15 16V7.208zM15 0H1v1.89h14z'/%3E%3C/svg%3E" />
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M15 3.604H1v1.891h14v-1.89ZM1 7.208V16l7-3.926L15 16V7.208zM15 0H1v1.89h14z'/%3E%3C/svg%3E"/>
<meta name="base-uri" content="{{ base_uri }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="folders" content="{{ folders|json_encode }}">
<title>Log Viewer for Symfony</title>
<link href="{{ asset('style.css', 'fd_symfony_log_viewer') }}" rel="stylesheet"/>
<link href="{{ assets.style }}" rel="stylesheet"/>
{# Toggle dark mode based on system settings #}
<script>document.documentElement.dataset.bsTheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';</script>
</head>
<body class="h-100 overflow-hidden">
<div id="log-viewer" class="slv-body-grid container-fluid p-0 h-100"></div>
<script type="module" src="{{ asset('src/main.ts', 'fd_symfony_log_viewer') }}"></script>
<script type="module" src="{{ assets.js }}"></script>
</body>
</html>
5 changes: 3 additions & 2 deletions src/Service/File/LogParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
use FD\LogViewer\Iterator\LogRecordIterator;
use FD\LogViewer\Iterator\MaxRuntimeIterator;
use FD\LogViewer\StreamReader\StreamReaderFactory;
use Psr\Clock\ClockInterface;
use SplFileInfo;

class LogParser
{
private const MAX_RUNTIME_IN_SECONDS = 10;

public function __construct(private readonly StreamReaderFactory $streamReaderFactory)
public function __construct(private readonly ClockInterface $clock, private readonly StreamReaderFactory $streamReaderFactory)
{
}

Expand All @@ -27,7 +28,7 @@ public function parse(SplFileInfo $file, LogLineParserInterface $lineParser, Log
// create iterators
$streamReader = $this->streamReaderFactory->createForFile($file, $logQuery->direction, $logQuery->offset);
$lineIterator = new LogLineParserIterator($streamReader, $lineParser, $logQuery->direction);
$iterator = new MaxRuntimeIterator($lineIterator, self::MAX_RUNTIME_IN_SECONDS, false);
$iterator = new MaxRuntimeIterator($this->clock, $lineIterator, self::MAX_RUNTIME_IN_SECONDS, false);
$iterator = new LogRecordIterator($iterator, $lineParser, $logQuery->query);
if ($logQuery->levels !== null || $logQuery->channels !== null) {
$iterator = new LogRecordFilterIterator($iterator, $logQuery->levels, $logQuery->channels);
Expand Down
Loading

0 comments on commit 6ddbd70

Please sign in to comment.