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 }); }