86 lines
3.8 KiB
JavaScript
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 });
|
|
}
|