add system log viewer
This commit is contained in:
@@ -2,9 +2,22 @@ monolog:
|
||||
channels: ['agent']
|
||||
|
||||
handlers:
|
||||
|
||||
# -------------------------------------------------
|
||||
# 1) Agent Channel (nur Agent Logs)
|
||||
# -------------------------------------------------
|
||||
agent:
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/agent.log'
|
||||
level: debug
|
||||
max_files: 14
|
||||
channels: ['agent']
|
||||
max_files: 30
|
||||
channels: ['agent']
|
||||
|
||||
# -------------------------------------------------
|
||||
# 2) System Log (wirklich alles)
|
||||
# -------------------------------------------------
|
||||
system:
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/system.log'
|
||||
level: debug
|
||||
max_files: 30
|
||||
101
src/Controller/Admin/AdminSystemLogController.php
Normal file
101
src/Controller/Admin/AdminSystemLogController.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
#[Route('/admin/system-logs')]
|
||||
final class AdminSystemLogController extends AbstractController
|
||||
{
|
||||
private string $logDir;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logDir = \dirname(__DIR__, 3) . '/var/log';
|
||||
}
|
||||
|
||||
#[Route('', name: 'admin_system_logs_index')]
|
||||
public function index(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
|
||||
|
||||
$files = [];
|
||||
|
||||
foreach (\glob($this->logDir . '/system*.log') ?: [] as $file) {
|
||||
$files[] = [
|
||||
'name' => \basename($file),
|
||||
'size' => \filesize($file),
|
||||
'mtime' => \filemtime($file),
|
||||
];
|
||||
}
|
||||
|
||||
\usort($files, fn($a, $b) => $b['mtime'] <=> $a['mtime']);
|
||||
|
||||
return $this->render('admin/system_logs/index.html.twig', [
|
||||
'files' => $files,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(
|
||||
'/view/{date}',
|
||||
name: 'admin_system_logs_view',
|
||||
requirements: ['date' => '\d{4}-\d{2}-\d{2}|current']
|
||||
)]
|
||||
public function view(string $date): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
|
||||
|
||||
if ($date === 'current') {
|
||||
$safeFilename = 'system.log';
|
||||
} else {
|
||||
$safeFilename = 'system-' . $date . '.log';
|
||||
}
|
||||
|
||||
$path = $this->logDir . '/' . $safeFilename;
|
||||
|
||||
if (!\file_exists($path)) {
|
||||
throw $this->createNotFoundException('Log file not found.');
|
||||
}
|
||||
|
||||
$content = $this->readLastLines($path, 1000);
|
||||
|
||||
return $this->render('admin/system_logs/view.html.twig', [
|
||||
'filename' => $safeFilename,
|
||||
'content' => $content,
|
||||
]);
|
||||
}
|
||||
|
||||
private function readLastLines(string $file, int $lines = 1000): string
|
||||
{
|
||||
$handle = \fopen($file, 'rb');
|
||||
if (!$handle) {
|
||||
return 'Unable to open log file.';
|
||||
}
|
||||
|
||||
$buffer = '';
|
||||
$chunkSize = 4096;
|
||||
$lineCount = 0;
|
||||
$pos = -1;
|
||||
|
||||
\fseek($handle, 0, SEEK_END);
|
||||
$fileSize = \ftell($handle);
|
||||
|
||||
while ($lineCount < $lines && -$pos < $fileSize) {
|
||||
$step = \min($chunkSize, $fileSize + $pos);
|
||||
\fseek($handle, $pos - $step, SEEK_END);
|
||||
$chunk = \fread($handle, $step);
|
||||
$buffer = $chunk . $buffer;
|
||||
$lineCount += \substr_count($chunk, "\n");
|
||||
$pos -= $step;
|
||||
}
|
||||
|
||||
\fclose($handle);
|
||||
|
||||
$linesArray = \explode("\n", $buffer);
|
||||
return \implode("\n", \array_slice($linesArray, -$lines));
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,11 @@
|
||||
</a>
|
||||
<a class="nav-link text-light {% if route starts with 'admin_job' %}active fw-bold{% endif %}"
|
||||
href="{{ path('admin_vector_log') }}">
|
||||
Vector-Logs Python
|
||||
Vector-Log Python
|
||||
</a>
|
||||
<a class="nav-link text-light {% if route starts with 'admin_job' %}active fw-bold{% endif %}"
|
||||
href="{{ path('admin_system_logs_index') }}">
|
||||
System-Logs
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
48
templates/admin/system_logs/index.html.twig
Normal file
48
templates/admin/system_logs/index.html.twig
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends 'admin/base.html.twig' %}
|
||||
|
||||
{% block title %}System Logs{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">System Logs</h1>
|
||||
</div>
|
||||
|
||||
<div class="card bg-dark border-secondary">
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-dark table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datei</th>
|
||||
<th>Größe</th>
|
||||
<th>Letzte Änderung</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for file in files %}
|
||||
<tr>
|
||||
<td>{% set date = file.name == 'system.log'
|
||||
? 'current'
|
||||
: file.name|replace({'system-':'','\.log':''}) %}
|
||||
{{ file.name }}</td>
|
||||
<td>{{ (file.size / 1024)|number_format(2) }} KB</td>
|
||||
<td>{{ file.mtime|date('Y-m-d H:i:s') }}</td>
|
||||
<td>
|
||||
<a href="{{ path('admin_system_logs_view', { date: date }) }}"
|
||||
class="btn btn-sm btn-outline-info">
|
||||
Anzeigen
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted p-3">
|
||||
Keine Logdateien gefunden.
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
61
templates/admin/system_logs/view.html.twig
Normal file
61
templates/admin/system_logs/view.html.twig
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends 'admin/base.html.twig' %}
|
||||
|
||||
{% block title %}System Log - {{ filename }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1 class="h5 mb-0">{{ filename }}</h1>
|
||||
|
||||
<input type="text"
|
||||
id="logSearch"
|
||||
class="form-control form-control-sm"
|
||||
placeholder="Search..."
|
||||
style="max-width:300px;">
|
||||
</div>
|
||||
|
||||
<div class="card bg-black border-secondary">
|
||||
<div class="card-body p-0">
|
||||
<pre id="logContent"
|
||||
style="margin:0;padding:15px;background:#000;color:#00ff88;
|
||||
font-size:12px;line-height:1.4;
|
||||
max-height:75vh;overflow:auto;white-space:pre-wrap;">
|
||||
{{ content }}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
|
||||
const input = document.getElementById('logSearch');
|
||||
const logElement = document.getElementById('logContent');
|
||||
|
||||
const originalText = logElement.textContent;
|
||||
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function highlight(term) {
|
||||
if (!term) {
|
||||
logElement.innerHTML = originalText;
|
||||
return;
|
||||
}
|
||||
|
||||
const safeTerm = escapeRegExp(term);
|
||||
const regex = new RegExp('(' + safeTerm + ')', 'gi');
|
||||
|
||||
logElement.innerHTML = originalText.replace(regex, function(match) {
|
||||
return '<span style="background:#ffcc00;color:#000;">' + match + '</span>';
|
||||
});
|
||||
}
|
||||
|
||||
input.addEventListener('input', function () {
|
||||
highlight(this.value.trim());
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user