diff --git a/src/logger.js b/src/logger.js index 9c8b937..f9611bf 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,6 +1,52 @@ // ==LOGGER START== (function () { + /** + * AI Repo Commander — Logger module + * + * Purpose + * - Provide structured, level-gated logging for all modules. + * - Buffer recent log entries for the in-page debug panel and copy/export. + * - Prevent log spam from hot paths via logLoop. + * + * Integration + * - Exposed as window.AI_REPO_LOGGER for use across modules. + * - Uses window.AI_REPO_CONFIG for runtime toggles and limits: + * - debug.enabled: boolean gate for all logging + * - debug.level: 0–5 (0=off, 1=error, 2=warn, 3=info, 4=verbose, 5=trace) + * - debug.watchMs: time window used by logLoop for anti-spam + * - debug.maxLines: max entries retained in memory buffer + * + * Console format + * - Each entry prints to the browser console as: [AI RC ] + * - Data is sanitized to avoid dumping large strings or DOM nodes. + * + * Notes + * - No external dependencies, works in plain browser context. + * - This module does not persist logs; it keeps an in-memory ring buffer only. + * + * Example + * const log = window.AI_REPO_LOGGER; + * log.info('Starting'); + * log.warn('Slow operation', { ms: 1234 }); + * log.error('Failed', { error: e.message }); + * console.log(log.getRecentLogs(100)); + */ + + /** + * Structured logger with in-memory buffer and anti-spam utilities. + * Fields: + * - config: ConfigManager; access to debug.* keys + * - buffer: Array<{timestamp, level, message, data}> recent log entries + * - loopCounts: Map used by logLoop to cap repeated messages + * - startedAt: number (ms) reference time for logLoop watch window + */ class Logger { + /** + * Initializes the logger. + * - Grabs the global config instance. + * - Prepares the in-memory buffer and loop counters. + * - Starts a periodic cleanup that resets logLoop counters after ~2× watch window. + */ constructor() { this.config = window.AI_REPO_CONFIG; this.buffer = []; @@ -17,6 +63,8 @@ }, this.config.get('debug.watchMs') || 120000); } + // Convenience level helpers. Gated by debug.enabled and debug.level (see _log). + // Levels: 1=ERROR, 2=WARN, 3=INFO, 4=VERBOSE, 5=TRACE error(msg, data) { this._log(1, 'ERROR', msg, data); } warn(msg, data) { this._log(2, 'WARN', msg, data); } info(msg, data) { this._log(3, 'INFO', msg, data); } @@ -80,6 +128,24 @@ else this.info(`${msg}${suffix}`); } + /** + * Core logging sink. Applies gating, buffers the entry, and prints to console. + * + * Gating + * - If debug.enabled is false, nothing is logged. + * - If levelNum > debug.level, the entry is ignored. + * + * Buffering + * - Recent entries kept in memory (debug.maxLines ring buffer) for the debug panel and copy/export. + * + * Console output + * - Printed as: [AI RC ] message [sanitized data] + * + * @param {number} levelNum - Numeric level (1..5) + * @param {string} levelName - Label shown in output (ERROR/WARN/INFO/VERBOSE/TRACE) + * @param {any} msg - Primary message; coerced to string + * @param {any} [data] - Optional context data; sanitized to avoid huge strings/DOM elements + */ _log(levelNum, levelName, msg, data) { const enabled = !!this.config.get('debug.enabled'); const level = this.config.get('debug.level') ?? 0; @@ -99,6 +165,19 @@ entry.data ? console.log(prefix, msg, entry.data) : console.log(prefix, msg); } + /** + * Best-effort redaction/sanitization of context data before logging. + * - HTMLElement → "HTMLElement" (prevents dumping live DOM trees) + * - Long strings (>200 chars) → truncated with ellipsis to keep logs concise + * - Plain objects → shallowly sanitize each value using the same rules + * - Other primitives are returned as-is + * + * This keeps console output readable and reduces accidental leakage of + * large payloads while still conveying useful context. + * + * @param {any} data + * @returns {any} sanitized data suitable for console/JSON + */ _sanitize(data) { if (!data) return null; if (data instanceof HTMLElement) return `HTMLElement<${data.tagName}>`; @@ -114,12 +193,29 @@ return data; } + /** + * Returns the most recent N logs as plain text, one entry per line. + * Each line format: ISO_TIMESTAMP LEVEL message {jsonData?} + * + * @param {number} [n=50] - Number of lines to include from the tail of the buffer + * @returns {string} + */ getRecentLogs(n = 50) { return this.buffer.slice(-n).map(e => `${e.timestamp} ${e.level.padEnd(7)} ${e.message}${e.data ? ' ' + JSON.stringify(e.data) : ''}` ).join('\n'); } + /** + * Sets the runtime log level in config (0–5) and logs the change. + * Levels: + * 0=off, 1=error, 2=warn, 3=info, 4=verbose, 5=trace + * + * Note: The debug panel level selector typically writes to the same key. + * This method is provided for console/automation convenience. + * + * @param {number} n - Desired level; values are clamped to [0,5] + */ setLevel(n) { const lv = Math.max(0, Math.min(5, n)); this.config.set('debug.level', lv);