Lumi/scripts/verify-logging.js
2026-07-18 20:24:26 +02:00

86 lines
3.8 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const root = path.join(__dirname, "..");
const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-logging-"));
const serviceDir = path.join(sandbox, "src", "services");
fs.mkdirSync(serviceDir, { recursive: true });
for (const file of ["db.js", "logger.js", "web-events.js"]) {
fs.copyFileSync(path.join(root, "src", "services", file), path.join(serviceDir, file));
}
let database;
try {
database = require(path.join(serviceDir, "db.js"));
database.db.exec(`CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
level TEXT NOT NULL,
message TEXT NOT NULL,
details TEXT,
created_at INTEGER NOT NULL
)`);
database.migrate();
const logger = require(path.join(serviceDir, "logger.js"));
const { db } = database;
const columns = db.prepare("PRAGMA table_info(logs)").all().map((column) => column.name);
for (const column of ["source", "category", "event", "request_id"]) {
assert(columns.includes(column), `logs.${column} migration`);
}
logger.log("info", "Legacy-compatible entry", { status: "ready" });
const pluginLog = logger.createLogger("plugin:example", { category: "plugin" });
pluginLog.warn("Example warning", {
access_token: "must-not-be-stored",
nested: { password: "also-secret", safe: "visible" },
url: "https://example.com/run?token=hidden&mode=safe"
}, { event: "example_warning", requestId: "request-123" });
logger.withLogContext({ source: "core:test", category: "verification", event: "context_entry" }, () => {
logger.log("debug", "Context-aware entry", "authorization=private-value");
});
const all = logger.listLogs({ limit: 20 });
assert.equal(all.length, 3);
const warning = all.find((entry) => entry.event === "example_warning");
assert.equal(warning.source, "plugin:example");
assert.equal(warning.category, "plugin");
assert.equal(warning.request_id, "request-123");
assert(warning.details.includes("[REDACTED]"));
assert.equal(warning.details.includes("must-not-be-stored"), false);
assert.equal(warning.details.includes("also-secret"), false);
assert.equal(warning.details.includes("token=hidden"), false);
assert(warning.details.includes("visible"));
assert.equal(logger.listLogs({ sources: ["plugin:example"] }).length, 1);
assert.equal(logger.listLogs({ categories: ["verification"] }).length, 1);
assert.equal(logger.listLogs({ search: "request-123" }).length, 1);
assert.equal(logger.listLogs({ levels: ["error"] }).length, 0);
const summary = logger.summarizeLogs({});
assert.equal(summary.total, 3);
assert.equal(summary.levels.warn, 1);
assert(logger.listLogFacets().sources.some((item) => item.value === "plugin:example" && item.count === 1));
db.prepare("UPDATE logs SET created_at = ? WHERE message = ?").run(
Date.now() - 45 * 24 * 60 * 60 * 1000,
"Legacy-compatible entry"
);
const cleanup = logger.cleanupLogs({ maxAgeDays: 30, maxEntries: 1000 });
assert.equal(cleanup.expired, 1);
assert.equal(logger.listLogs({ search: "Legacy-compatible" }).length, 0);
const logView = fs.readFileSync(path.join(root, "src", "web", "views", "admin-logs.ejs"), "utf8");
assert.match(logView, /data-log-source/);
assert.match(logView, /data-log-category/);
assert.match(logView, /data-log-live-status/);
assert.match(logView, /name="format"/);
const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
assert.match(serverSource, /app\.post\("\/admin\/logs\/retention"/);
assert.match(serverSource, /summarizeLogs\(query\)/);
assert.match(serverSource, /"admin_action"/);
console.log("Logging verification passed: structured scope, redaction, search, summaries, retention, admin audit, and live UI wiring.");
} finally {
try { database?.db?.close(); } catch {}
fs.rmSync(sandbox, { recursive: true, force: true });
}