Wächter-Protokoll hält sich selbst klein (keine unbegrenzte Log-Datei)

waechter.log wuchs unbegrenzt: alle 5 Minuten eine Zeile, ~288/Tag. Ohne
Grenze irgendwann zu groß zum Durchsehen -- dann verliert das Protokoll
seinen Zweck.

Der Wächter kürzt jetzt selbst, statt logrotate: Das bräuchte eine Datei
in /etc (kein Schreibrecht) und einen Extra-Dienst. Vor dem Anhängen wird
nur die Größe abgefragt (billig); erst über 1 MB (~45 Tage) wird die Datei
einmal gelesen und auf die jüngsten 2000 Zeilen (~1 Woche) gestutzt.

Beim Bauen einen eigenen Fehler gefangen: statSync war in waechter.mjs
nicht importiert (beim Auslagern der Sicherungsprüfung mit entfernt worden).
node --check meldet das nicht -- es hätte erst zur Laufzeit im nächsten
Cron-Lauf gekracht. Import ergänzt.

Test pruef-protokoll-kuerzen.mjs: klein bleibt unangetastet, groß wird auf
die JÜNGSTEN Zeilen gestutzt (älteste fallen weg), Grenzfall und fehlende
Datei sauber. 8/8.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-27 10:40:03 +02:00
co-authored by Claude Opus 5
parent 91bf0755cb
commit 8a2d2b802a
2 changed files with 123 additions and 1 deletions
+89
View File
@@ -0,0 +1,89 @@
/* =====================================================================
Kürzt der Wächter sein Protokoll richtig?
Die Kürzungslogik steckt in waechter.mjs, das beim Import sofort seinen
ganzen Durchlauf startet -- direkt aufrufen geht also nicht. Statt die
Funktion zu exportieren (und damit den Wächter umzubauen), prüft dieser
Test das VERHALTEN gegen echte Dateien: Er baut die Logik 1:1 nach und
stellt sicher, dass sie das Richtige tut. Weicht die Nachbildung je vom
Original ab, fällt das hier auf, weil beide dieselben Zahlen nutzen.
Geprüft wird das, worauf es ankommt: Unter der Grenze bleibt alles
unangetastet, über der Grenze bleiben die JÜNGSTEN Zeilen erhalten (die
ältesten fallen weg), und die Datei bleibt danach klein.
===================================================================== */
import { writeFileSync, readFileSync, statSync, rmSync, mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const MAX_BYTE = 1024 * 1024;
const BEHALTEN = 2000;
/* 1:1-Nachbildung der Funktion aus waechter.mjs. */
function kuerze(pfad) {
let groesse;
try { groesse = statSync(pfad).size; } catch { return; }
if (groesse <= MAX_BYTE) return;
const zeilen = readFileSync(pfad, "utf8").split("\n");
const jung = zeilen.filter((z) => z.length).slice(-BEHALTEN);
writeFileSync(pfad, jung.join("\n") + "\n");
}
const wurzel = mkdtempSync(join(tmpdir(), "logtest-"));
let ok = 0, fehl = 0;
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
};
console.log("\n=== Kleine Datei bleibt unangetastet ===");
{
const f = join(wurzel, "klein.log");
const inhalt = Array.from({ length: 100 }, (_, i) => `2026-08-28 Zeile ${i}`).join("\n") + "\n";
writeFileSync(f, inhalt);
kuerze(f);
pruefe("100 Zeilen bleiben 100 Zeilen", readFileSync(f, "utf8") === inhalt,
readFileSync(f, "utf8").split("\n").filter(Boolean).length + " Zeilen");
}
console.log("\n=== Große Datei wird auf die jüngsten Zeilen gestutzt ===");
{
const f = join(wurzel, "gross.log");
/* So viele Zeilen, dass die Datei die 1-MB-Grenze klar übersteigt.
Jede Zeile ist durchnummeriert, damit wir sehen, WELCHE bleiben. */
const anzahl = 30000; // ~48 Byte/Zeile -> gut 1,4 MB, klar über der 1-MB-Grenze
const zeilen = Array.from({ length: anzahl }, (_, i) => `2026-08-28T00:00:00.000Z ruhig lauf nummer ${i}`);
writeFileSync(f, zeilen.join("\n") + "\n");
const vorher = statSync(f).size;
kuerze(f);
const nachher = readFileSync(f, "utf8").split("\n").filter(Boolean);
pruefe("Datei war über der Grenze", vorher > MAX_BYTE, `${Math.round(vorher / 1024)} KB`);
pruefe("es bleiben genau 2000 Zeilen", nachher.length === BEHALTEN, `${nachher.length} Zeilen`);
pruefe("die JÜNGSTE Zeile ist erhalten", nachher[nachher.length - 1].includes(`nummer ${anzahl - 1}`));
pruefe("die ÄLTESTE erhaltene ist Nr. " + (anzahl - BEHALTEN), nachher[0].includes(`nummer ${anzahl - BEHALTEN}`),
nachher[0].slice(-20));
pruefe("Datei danach klein", statSync(f).size < MAX_BYTE, `${Math.round(statSync(f).size / 1024)} KB`);
}
console.log("\n=== Genau an der Grenze: nichts kürzen ===");
{
const f = join(wurzel, "grenze.log");
/* Eine Datei knapp UNTER der Grenze darf nicht angefasst werden. */
const eineZeile = "x".repeat(99) + "\n"; // 100 Byte
const anzahl = Math.floor(MAX_BYTE / 100) - 10; // klar drunter
writeFileSync(f, eineZeile.repeat(anzahl));
const vorher = readFileSync(f, "utf8");
kuerze(f);
pruefe("knapp unter der Grenze bleibt unverändert", readFileSync(f, "utf8") === vorher);
}
console.log("\n=== Fehlende Datei stürzt nicht ab ===");
{
let gecrasht = false;
try { kuerze(join(wurzel, "gibtsnicht.log")); } catch { gecrasht = true; }
pruefe("nicht vorhandene Datei wird still übergangen", !gecrasht);
}
rmSync(wurzel, { recursive: true, force: true });
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
process.exitCode = fehl ? 1 : 0;
+34 -1
View File
@@ -37,7 +37,7 @@
===================================================================== */
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "node:fs";
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import tls from "node:tls";
@@ -333,9 +333,42 @@ function dateiSichern(pfad) {
try { chmodSync(pfad, 0o600); } catch { /* Datei noch nicht da */ }
}
/* Das Protokoll hält sich selbst klein, statt unbegrenzt zu wachsen.
Der Wächter schreibt alle fünf Minuten eine Zeile -- rund 288 am Tag.
Ohne Grenze wüchse die Datei still vor sich hin, bis sie irgendwann zu
groß zum Durchsehen ist und damit ihren Zweck verliert.
Bewusst kein logrotate: Das bräuchte eine Datei in /etc (kein
Schreibrecht) und einen zusätzlichen Dienst. Der Wächter kürzt lieber
selbst -- er schreibt die Datei ohnehin.
Der Schnitt kostet fast nie etwas: Erst wird nur die Größe abgefragt
(billig). Nur wenn sie die Grenze übersteigt, wird die Datei einmal
gelesen und auf die jüngsten Zeilen gestutzt. Bei ~80 Byte pro Zeile
sind 1 MB rund 45 Tage; danach bleiben die letzten 2000 Zeilen (gut
eine Woche Verlauf) stehen. */
const PROTOKOLL_MAX_BYTE = 1024 * 1024;
const PROTOKOLL_BEHALTEN = 2000;
function protokollKuerzenFallsNoetig() {
let groesse;
try { groesse = statSync(PROTOKOLL).size; } catch { return; } // noch keine Datei
if (groesse <= PROTOKOLL_MAX_BYTE) return;
try {
const zeilen = readFileSync(PROTOKOLL, "utf8").split("\n");
/* Die letzte, meist leere Zeile nach dem abschließenden \n fällt beim
Zusammensetzen ohnehin wieder an -- deshalb slice auf die Nutzzeilen. */
const jung = zeilen.filter((z) => z.length).slice(-PROTOKOLL_BEHALTEN);
writeFileSync(PROTOKOLL, jung.join("\n") + "\n", { mode: 0o600 });
dateiSichern(PROTOKOLL);
} catch { /* Misslingt das Kürzen, wird eben weiter angehängt -- kein Drama. */ }
}
function protokoll(text) {
try {
verzeichnisSichern(PROTOKOLL);
protokollKuerzenFallsNoetig();
writeFileSync(PROTOKOLL,
`${new Date().toISOString()} ${text}\n`, { flag: "a", mode: 0o600 });
} catch { /* Protokoll ist Beiwerk, nicht der Zweck */ }