Skip to content

Commit

Permalink
Merge pull request #174 from Florisbosch/main
Browse files Browse the repository at this point in the history
Refactor file operations to Laravel's illuminate/filesystem
  • Loading branch information
arukompas authored Dec 17, 2022
2 parents 3adaffb + 377ef12 commit ebbfd0e
Show file tree
Hide file tree
Showing 14 changed files with 85 additions and 59 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"require": {
"php": "^8.0",
"illuminate/contracts": "^8.0|^9.0",
"illuminate/filesystem": "^8.0|^9.0",
"livewire/livewire": "^2.10"
},
"require-dev": {
Expand Down
11 changes: 11 additions & 0 deletions config/log-viewer.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@

'middleware' => ['web'],

/*
|--------------------------------------------------------------------------
| Filesystem for logs
|--------------------------------------------------------------------------
|
*/
'filesystem' => [
'root' => env('LOG_VIEWER_FILESYSTEM_ROOT', ''),
'disk' => env('LOG_VIEWER_FILESYSTEM_DISK', 'log-viewer-local'),
],

/*
|--------------------------------------------------------------------------
| Include file patterns
Expand Down
2 changes: 2 additions & 0 deletions src/Facades/LogViewer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Opcodes\LogViewer\Facades;

use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Facade;
use Opcodes\LogViewer\LogFile;
use Opcodes\LogViewer\LogFileCollection;
Expand All @@ -19,6 +20,7 @@
* @method static void clearFileCache()
* @method static string|null getRouteDomain()
* @method static array getRouteMiddleware()
* @method static Filesystem getFilesystem()
* @method static string getRoutePrefix()
* @method static void auth($callback = null)
* @method static void setMaxLogSize(int $bytes)
Expand Down
1 change: 1 addition & 0 deletions src/Http/Livewire/FileList.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public function deleteFolder(string $folderIdentifier)
{
$folder = LogViewer::getFolder($folderIdentifier);


if ($folder) {
Gate::authorize('deleteLogFolder', $folder);

Expand Down
16 changes: 8 additions & 8 deletions src/LogFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use Illuminate\Support\Str;
use Opcodes\LogViewer\Events\LogFileDeleted;
use Opcodes\LogViewer\Exceptions\InvalidRegularExpression;
use Opcodes\LogViewer\Facades\LogViewer;
use Opcodes\LogViewer\Utils\Utils;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;

class LogFile
{
Expand Down Expand Up @@ -49,9 +51,7 @@ public function logs(): LogReader

public function size(): int
{
clearstatcache();

return filesize($this->path);
return LogViewer::getFilesystem()->size($this->path);
}

public function sizeInMB(): float
Expand All @@ -74,9 +74,9 @@ public function downloadUrl(): string
return route('blv.download-file', $this->identifier);
}

public function download(): BinaryFileResponse
public function download(): StreamedResponse
{
return response()->download($this->path);
return LogViewer::getFilesystem()->download($this->path);
}

public function addRelatedIndex(LogIndex $logIndex): void
Expand Down Expand Up @@ -104,13 +104,13 @@ public function getLastScannedFilePositionForQuery(?string $query = ''): ?int
public function earliestTimestamp(): int
{
return $this->getMetadata('earliest_timestamp')
?? (is_file($this->path) ? filemtime($this->path) : 0);
?? LogViewer::getFilesystem()->lastModified($this->path);
}

public function latestTimestamp(): int
{
return $this->getMetadata('latest_timestamp')
?? (is_file($this->path) ? filemtime($this->path) : 0);
?? LogViewer::getFilesystem()->lastModified($this->path);
}

public function scan(int $maxBytesToScan = null, bool $force = false): void
Expand All @@ -134,7 +134,7 @@ public function search(string $query = null): LogReader
public function delete(): void
{
$this->clearCache();
unlink($this->path);
LogViewer::getFilesystem()->delete($this->path);
LogFileDeleted::dispatch($this);
}
}
2 changes: 1 addition & 1 deletion src/LogFolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public function download(): BinaryFileResponse
/** @var LogFile $file */
foreach ($this->files() as $file) {
if (Gate::check('downloadLogFile', $file)) {
$zip->addFile($file->path, $file->name);
$zip->addFromString(name: $file->name, content: LogViewer::getFilesystem()->get($file->path));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/LogReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public function open(): self
return $this;
}

$this->fileHandle = fopen($this->file->path, 'r');
$this->fileHandle = LogViewer::getFilesystem()->readStream($this->file->path);

if ($this->fileHandle === false) {
throw new \Exception('Could not open "'.$this->file->path.'" for reading.');
Expand Down
62 changes: 33 additions & 29 deletions src/LogViewerService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

use Composer\InstalledVersions;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Finder\Glob;

class LogViewerService
{
Expand All @@ -20,30 +23,20 @@ class LogViewerService

protected function getFilePaths(): array
{
// Because we'll use the base path as a parameter for `glob`, we should escape any
// glob's special characters and treat those as actual characters of the path.
// We can assume this, because it's the actual path of the Laravel app, not a user-defined
// search pattern.
if (PHP_OS_FAMILY === 'Windows') {
$baseDir = str_replace(
['[', ']'],
['{LEFTBRACKET}', '{RIGHTBRACKET}'],
str_replace('\\', '/', $this->basePathForLogs())
);
$baseDir = str_replace(
['{LEFTBRACKET}', '{RIGHTBRACKET}'],
['[[]', '[]]'],
$baseDir
);
} else {
$baseDir = str_replace(
['*', '?', '\\', '[', ']'],
['\*', '\?', '\\\\', '\[', '\]'],
$this->basePathForLogs()
);
}
$files = [];

// Because we use the Glob::toRegex function we have to escape the brackets
$baseDir = str_replace(
['[', ']'],
['{LEFTBRACKET}', '{RIGHTBRACKET}'],
str_replace('\\', '/', $this->basePathForLogs())
);
$baseDir = str_replace(
['{LEFTBRACKET}', '{RIGHTBRACKET}'],
['[[]', '[]]'],
$baseDir
);

foreach (config('log-viewer.include_files', []) as $pattern) {
if (! str_starts_with($pattern, DIRECTORY_SEPARATOR)) {
$pattern = $baseDir.$pattern;
Expand All @@ -60,23 +53,29 @@ protected function getFilePaths(): array
$files = array_diff($files, $this->getFilePathsMatchingPattern($pattern));
}

$files = array_map('realpath', $files);

$files = array_filter($files, 'is_file');

return array_values(array_reverse($files));
}

protected function getFilePathsMatchingPattern($pattern)
{
// The GLOB_BRACE flag is not available on some non GNU systems, like Solaris or Alpine Linux.
$files = [];

return glob($pattern);
foreach($this->getFilesystem()->allFiles($this->basePathForLogs()) as $file)
{
if (preg_match(pattern: Glob::toRegex(glob: $pattern), subject: $file)) {
$files[] = $file;
}
}

return $files;
}

public function basePathForLogs(): string
{
return Str::finish(realpath(storage_path('logs')), DIRECTORY_SEPARATOR);
$rootFolder = Str::of(config('log-viewer.filesystem.root'));
return empty($rootFolder)
? $rootFolder->finish('/')
: $rootFolder;
}

/**
Expand Down Expand Up @@ -154,6 +153,11 @@ public function getRouteMiddleware(): array
return config('log-viewer.middleware', []) ?: ['web'];
}

public function getFilesystem(): Filesystem
{
return Storage::disk(config('log-viewer.filesystem.disk'));
}

public function auth($callback = null): void
{
if (is_null($callback) && isset($this->authCallback)) {
Expand Down
5 changes: 5 additions & 0 deletions src/LogViewerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public function register()
{
$this->mergeConfigFrom($this->basePath("/config/{$this->name}.php"), $this->name);

$this->app['config']['filesystems.disks.log-viewer-local'] = [
'driver' => 'local',
'root' => storage_path('logs'),
];

$this->app->bind('log-viewer', LogViewerService::class);
$this->app->bind('log-viewer-cache', function () {
return Cache::driver(config('log-viewer.cache_driver'));
Expand Down
13 changes: 7 additions & 6 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Carbon\CarbonInterface;
use Illuminate\Support\Facades\File;
use Opcodes\LogViewer\Facades\LogViewer;
use Opcodes\LogViewer\LogFile;
use Opcodes\LogViewer\LogIndex;
use Opcodes\LogViewer\Tests\TestCase;
Expand Down Expand Up @@ -34,18 +35,18 @@ function generateLogFile(string $fileName = null, string $content = null, bool $
$fileName = \Illuminate\Support\Str::random().'.log';
}

$path = storage_path('logs/'.$fileName);
$storage = LogViewer::getFilesystem();

if (File::exists($path)) {
File::delete($path);
if ($storage->exists($fileName)) {
$storage->delete($fileName);
}

File::put($path, $content ?? ($randomContent ? dummyLogData() : ''));
$storage->put($fileName, $content ?? ($randomContent ? dummyLogData() : ''));

// we perform a regular PHP assertion, so it doesn't count towards the unit test assertion count.
assert(file_exists($path));
assert($storage->exists($fileName));

return new LogFile($path);
return new LogFile($fileName);
}

function dummyLogData(int $lines = null): string
Expand Down
12 changes: 5 additions & 7 deletions tests/Unit/FilePathsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@

test('handles square brackets in the logs path', function ($folderPath) {
// Get the original path inside which we'll create a dummy folder with square brackets
$storage = LogViewer::getFilesystem();
$originalBasePath = LogViewer::basePathForLogs();
$pathWithSquareBrackets = $originalBasePath.$folderPath.DIRECTORY_SEPARATOR;
if (! file_exists($pathWithSquareBrackets)) {
mkdir($pathWithSquareBrackets, recursive: true);
}

// Let's mock LogViewer to return the new path as the base path for logs
app()->instance(
Expand All @@ -21,8 +19,8 @@

// Create a dummy log file and make sure it's actually there
$expectedLogFilePath = $pathWithSquareBrackets.($fileName = 'laravel.log');
touch($expectedLogFilePath);
expect(file_exists($expectedLogFilePath))->toBeTrue();
$storage->put($expectedLogFilePath, '');
expect($storage->exists($expectedLogFilePath))->toBeTrue();

// Act! Let's get the files and make sure they have found the log file created previously.
$logFiles = LogViewer::getFiles();
Expand All @@ -32,8 +30,8 @@
->and($logFiles[0]->path)->toBe($expectedLogFilePath);

// clean up!
unlink($expectedLogFilePath);
rmdir($pathWithSquareBrackets);
$storage->delete($expectedLogFilePath);
$storage->deleteDirectory($pathWithSquareBrackets);
})->with([
'[logs]',
'[logs',
Expand Down
9 changes: 5 additions & 4 deletions tests/Unit/LogFileTest.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php

use Opcodes\LogViewer\Facades\LogViewer;
use Opcodes\LogViewer\LogFile;

test('log file can be instantiated with just a path to the file', function () {
$path = storage_path('logs/laravel.log');
file_put_contents($path, str_repeat('0', 10)); // 10 bytes
$filename = 'laravel.log';
LogViewer::getFilesystem()->put($filename, str_repeat('0', 10));

$logFile = new LogFile($path);
$logFile = new LogFile($filename);

expect($logFile->path)->toBe($path)
expect($logFile->path)->toBe($filename)
->and($logFile->name)->toBe('laravel.log')
->and($logFile->size())->toBe(10);
});
3 changes: 2 additions & 1 deletion tests/Unit/LogIndex/LogIndexTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Opcodes\LogViewer\Facades\Cache;
use Opcodes\LogViewer\Facades\LogViewer;
use Opcodes\LogViewer\LogFile;
use Opcodes\LogViewer\Utils\GenerateCacheKey;

Expand Down Expand Up @@ -206,7 +207,7 @@
expect($logIndex->incomplete())->toBeFalse();

// if we add some data to the file, the log index should be considered incomplete
file_put_contents($logFile->path, makeLogEntry());
LogViewer::getFilesystem()->put($logFile->path, makeLogEntry());
$logIndex = createLogIndex($logFile);
expect($logIndex->incomplete())->toBeTrue();

Expand Down
5 changes: 3 additions & 2 deletions tests/Unit/LogReaderTest.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<?php

use Illuminate\Support\Facades\File;
use Opcodes\LogViewer\Facades\LogViewer;
use Opcodes\LogViewer\LogFile;

beforeEach(function () {
$this->file = generateLogFile();
File::append($this->file->path, makeLogEntry());
LogViewer::getFilesystem()->append($this->file->path, makeLogEntry());
});

afterEach(fn () => clearGeneratedLogFiles());
Expand All @@ -26,7 +27,7 @@

\Spatie\TestTime\TestTime::addMinute();

File::append($this->file->path, PHP_EOL.makeLogEntry());
LogViewer::getFilesystem()->append($this->file->path, PHP_EOL.makeLogEntry());

// re-instantiate the log reader to make sure we don't have anything cached
$this->file = new LogFile($this->file->path);
Expand Down

0 comments on commit ebbfd0e

Please sign in to comment.