Simple Log File Viewer
Display log files in your application using Livewire and Alpine
I was having a lot of problems displaying log files in the admin area of my application. The Logfiles were being written to following each transaction, and a typical daily log file could be up to 10MB. The best available packages would crash and burn with this size of logfile.
I tried both rap2hpoutre/laravel-log-viewer and arcanedev/log-viewer and ended up building a simple viewer with pagination of the logs. Alpine is used to collapse stack trace.
In TALL stack style, the logfiles are displayed using Tailwind.

Livewire Component

php artisan make:livewire LogsViewer
app/Http/Livewire/LogsViewer.php
1
<?php
2
​
3
namespace App\Http\Livewire;
4
​
5
use Illuminate\Support\Facades\File;
6
use Livewire\Component;
7
use SplFileInfo;
8
​
9
class LogsViewer extends Component
10
{
11
public $file=0;
12
public $page=1;
13
public $total;
14
public $perPage = 500;
15
public $paginator;
16
​
17
protected $queryString=['page'];
18
​
19
public function render()
20
{
21
​
22
$files = $this->getLogfiles();
23
​
24
$log=collect(file($files[$this->file]->getPathname(), FILE_IGNORE_NEW_LINES));
25
​
26
$this->total = intval(floor($log->count() / $this->perPage)) + 1;
27
​
28
$log = $log->slice(($this->page - 1) * $this->perPage, $this->perPage)->values();
29
​
30
return view('livewire.logs-viewer')->withFiles($files)->withLog($log);
31
​
32
​
33
}
34
​
35
protected function getLogFiles()
36
{
37
$directory = storage_path('logs');
38
​
39
return collect(File::allFiles($directory))
40
->sortByDesc(function (SplFileInfo $file) {
41
return $file->getMTime();
42
})->values();
43
}
44
​
45
public function goto($page)
46
{
47
$this->page=$page;
48
}
49
​
50
public function updatingFile()
51
{
52
$this->page=1;
53
}
54
}
55
​
Copied!

View file

Of note here is the detection of the [stackdump] sections, moving these to a nested block and hiding that block using Alpine. Clicking the [stackdump] in the log expands the nested section and reveals the stack dump.
resources/views/livewire/logs-viewer.blade.php
1
<div>
2
<x-slot name="header">
3
<h2 class="text-xl font-semibold leading-tight text-gray-800">
4
Log Files
5
</h2>
6
</x-slot>
7
​
8
<div class="px-4 py-2 mx-4 my-8 bg-white shadow-xl sm:rounded-lg">
9
<div class="flex justify-around">
10
<select wire:model="file" class="px-4 py-2 font-mono text-sm bg-red-200 rounded">
11
@foreach($files as $file)
12
<option value="{{ $loop->index }}">{{ $file->getFilename() }}</option>
13
@endforeach
14
</select>
15
</div>
16
​
17
@include('layouts.logs-paginator')
18
19
@if($log->count()>0)
20
<ul class='font-mono text-xs'>
21
22
@for($i=0; $i < $log->count(); $i++)
23
@if(Illuminate\Support\Str::startsWith($log[$i],'[stacktrace]') || Illuminate\Support\Str::startsWith($log[$i],'#'))
24
<li x-data="{expanded:false}" x-on:click="expanded = !expanded">[stacktrace]
25
<ul class="ml-8" x-show="expanded" x-cloak >
26
@while($i < $log->count())
27
<li wire:key="{{$page}}-line-{{ $i }}">{{ $log[$i] }}</li>
28
@break(Illuminate\Support\Str::startsWith($log[$i++],'"}'))
29
@endwhile
30
</ul>
31
</li>
32
@endif
33
@break($i>=$log->count())
34
35
<li wire:key="{{ $page }}-line-{{ $i }}" class="font-mono text-xs leading-5
36
{{ Illuminate\Support\Str::contains($log[$i], '.CRITICAL:') ? 'text-red-800':''}}
37
{{ Illuminate\Support\Str::contains($log[$i], '.ERROR:') ? 'text-orange-600':'' }}
38
{{ Illuminate\Support\Str::contains($log[$i], '.INFO:') ? 'text-blue-900':'' }}
39
{{ Illuminate\Support\Str::contains($log[$i], '.WARNING:') ? 'text-indigo-700':'' }}
40
">{{ $log[$i] }}
41
</li>
42
@endfor
43
</ul>
44
@endif
45
</div>
46
​
47
48
</div>
49
​
Copied!

Paginator

resources/views/layouts/logs-paginator.blade.php
1
<div class="flex float-right">
2
<button id="first" wire:click="goto(1)" class="w-10 outline-none px-2 border rounded-l-lg m-0 {{ $page==1 ? 'bg-gray-600 text-white font-bold' :'' }}" >1</button>
3
4
@if($page-4 > 1)
5
<button id="dots1" class="w-10 px-2 m-0 -ml-px border outline-none">&hellip;</button>
6
@endif
7
​
8
@if($page-3 > 1)
9
<button id="minus3" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page-3 }})">{{ $page-3 }}</button>
10
@endif
11
@if($page-2 > 1)
12
<button id="minus2" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page-2 }})">{{ $page-2 }}</button>
13
@endif
14
@if($page-1 > 1)
15
<button id="minus1" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page-1 }})">{{ $page-1 }}</button>
16
@endif
17
​
18
@if($page != 1 && $page != $total )
19
<button id="current" class="w-10 px-2 m-0 -ml-px font-bold text-white bg-gray-600 border outline-none">{{ $page }}</button>
20
@endif
21
​
22
@if($page+1 < $total )
23
<button id="plus1" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page+1 }})">{{ $page+1 }}</button>
24
@endif
25
@if($page+2 < $total )
26
<button id="plus2" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page+2 }})">{{ $page+2 }}</button>
27
@endif
28
@if($page+3 < $total )
29
<button id="plus3" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page+3 }})">{{ $page+3 }}</button>
30
@endif
31
32
@if($page+4 < $total )
33
<button id="dots2" class="w-10 px-2 m-0 -ml-px border outline-none">&hellip;</button></button>
34
@endif
35
36
@if($total>1)
37
<button id="last" class="rounded-r-lg w-10 outline-none px-2 border -ml-px m-0 {{ $page == $total ? 'text-white bg-gray-600 font-bold':'' }}" wire:click="goto({{ $total }})">{{ $total }}</button>
38
@endif
39
​
40
</div>
Copied!
Add a protected link to your web.php file to the Logfile component.
​
Last modified 11mo ago