Protokollseite: die Logs der Hintergrundprozesse im Dashboard

Bisher kam man an die Ausgabe von solarManager.py, dem Runner und den
uebrigen Dauerprozessen nur per SSH heran. Die neue Seite unter
"Protokolle" zeigt sie: links die fuenf Dateien mit Groesse und Alter -
eine, die seit Tagen nicht gewachsen ist, faellt so auf, ohne dass man sie
oeffnet -, rechts die Zeilen mit Volltextsuche, Level-Filter und
optionalem Nachladen.

Nicht in die Datenbank geschrieben, obwohl das naheliegt: ein Log muss
genau dann noch funktionieren, wenn die Datenbank es nicht tut. Ein
Handler, der nach MySQL schreibt, verschluckt ausgerechnet die Meldung
"Datenbank nicht erreichbar" - also die, wegen der man nachsieht. Was
strukturiert ausgewertet werden soll, steht ohnehin in automation_log.

Gelesen wird rueckwaerts in Bloecken: 5000 Zeilen aus der 329 MB grossen
Datei der Wallbox kosten 46 ms, weil nur das Ende angefasst wird. Sucht
man ins Leere, bricht der Server nach 4 MB ab und sagt es auch.

Die beiden Prozesse formatieren unterschiedlich - der eine stellt das
Level voran, der andere den Zeitstempel -, deshalb wird beides gesucht
statt an fester Stelle erwartet. Zeilen ohne eigenes Level erben das der
Zeile darueber, sonst filterte "nur Fehler" die Tracebacks weg, die den
Fehler erklaeren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 19:26:56 +02:00
co-authored by Claude Opus 5
parent abfa84dff8
commit cbc9353f0a
7 changed files with 777 additions and 1 deletions
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* Endpunkt der Logseite.
*
* GET ?action=list Zustand aller bekannten Dateien
* GET ?action=read&datei=runner&... Zeilen einer Datei
*
* Parameter von read:
* datei Schluessel aus logDateien()
* anzahl wieviele Zeilen (1 bis 5000)
* suche Text, der vorkommen muss
* level Mindestlevel: INFO, WARNING, ERROR
*
* Ein reiner Verteiler - gelesen wird in restricted/logdateien.php.
*/
require_once("../helper.php");
require_once("../restricted/logdateien.php");
if (!checkLogin()) {
http_response_code(403);
exit;
}
function jsonAntwort($data, $code = 200)
{
http_response_code($code);
header("Content-Type: application/json; charset=utf-8");
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
exit;
}
$action = $_GET["action"] ?? "list";
try {
switch ($action) {
case "list":
$liste = [];
foreach (array_keys(logDateien()) as $key) {
$liste[] = logZustand($key);
}
jsonAntwort(["dateien" => $liste]);
case "read":
jsonAntwort(logLesen(
$_GET["datei"] ?? "",
$_GET["anzahl"] ?? 300,
trim((string) ($_GET["suche"] ?? "")),
strtoupper(trim((string) ($_GET["level"] ?? "")))
));
default:
jsonAntwort(["error" => "Unbekannte Aktion."], 400);
}
} catch (InvalidArgumentException $e) {
jsonAntwort(["error" => $e->getMessage()], 400);
} catch (Throwable $e) {
jsonAntwort(["error" => $e->getMessage()], 500);
}