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:
@@ -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;
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
===================================================================== */
|
===================================================================== */
|
||||||
|
|
||||||
import { execFileSync } from "node:child_process";
|
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 { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import tls from "node:tls";
|
import tls from "node:tls";
|
||||||
@@ -333,9 +333,42 @@ function dateiSichern(pfad) {
|
|||||||
try { chmodSync(pfad, 0o600); } catch { /* Datei noch nicht da */ }
|
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) {
|
function protokoll(text) {
|
||||||
try {
|
try {
|
||||||
verzeichnisSichern(PROTOKOLL);
|
verzeichnisSichern(PROTOKOLL);
|
||||||
|
protokollKuerzenFallsNoetig();
|
||||||
writeFileSync(PROTOKOLL,
|
writeFileSync(PROTOKOLL,
|
||||||
`${new Date().toISOString()} ${text}\n`, { flag: "a", mode: 0o600 });
|
`${new Date().toISOString()} ${text}\n`, { flag: "a", mode: 0o600 });
|
||||||
} catch { /* Protokoll ist Beiwerk, nicht der Zweck */ }
|
} catch { /* Protokoll ist Beiwerk, nicht der Zweck */ }
|
||||||
|
|||||||
Reference in New Issue
Block a user