UNPKG

xypriss-security

Version:

Advanced High-Performance Security Framework. Military-grade encryption, post-quantum resilience, and fortified data structures.

585 lines 24 kB
"use strict"; /** * Logger.ts * Centralized logger for FastApi.ts — structured, typed, and console-friendly. * * Output format: {gray timestamp} {color}[COMPONENT]{/} {color}message{/} * * Color logic: * - error → bright red (level always wins) * - warn → yellow (level always wins) * - debug → magenta (level always wins) * - verbose → gray (level always wins) * - info / startup / perf / etc. → component identity color */ Object.defineProperty(exports, "__esModule", { value: true }); exports.logger = exports.Logger = void 0; exports.initializeLogger = initializeLogger; exports.cleanupLogger = cleanupLogger; const DEFAULT_LOGGER_CONFIG_1 = require("./DEFAULT_LOGGER_CONFIG"); const DEFAULT_PALETTE_1 = require("./DEFAULT_PALETTE"); const mergeWithDefaults_1 = require("./mergeWithDefaults"); // ───────────────────────────────────────────── // ANSI palette // ───────────────────────────────────────────── // Internal shorthand for default colors const C = DEFAULT_PALETTE_1.DEFAULT_PALETTE; /** * Whether to emit ANSI codes. * We intentionally do NOT check isTTY — VS Code integrated terminal, Bun, and * most modern runners fully support ANSI but often report isTTY = false. * To disable colors, set the NO_COLOR env var (https://no-color.org). */ const canColor = () => typeof process === "undefined" || !("NO_COLOR" in process.env); // ───────────────────────────────────────────── // Level rank (no badge — we display component, not level label) // ───────────────────────────────────────────── const LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4, verbose: 5, }; /** * When the level is error / warn / debug / verbose the entire line uses this * color instead of the component identity color, so severity is unmistakable. */ const LEVEL_COLOR = { error: C.brightRed, warn: C.yellow, debug: C.magenta, verbose: C.gray, }; /** * Identity color per component — used for info-class messages where the level * does not override the color. */ const DEFAULT_COMPONENT_COLORS = { server: "\x1b[38;5;45m", // Bright Sky Blue cache: "\x1b[38;5;208m", // Vibrant Orange cluster: "\x1b[38;5;170m", // Soft Purple performance: "\x1b[38;5;201m", // Hot Pink fileWatcher: C.cyan, plugins: "\x1b[38;5;81m", // Light Blue security: "\x1b[93m", // Modern Yellow monitoring: C.green, routes: C.cyan, userApp: C.white, middleware: C.blue, router: C.cyan, typescript: C.blue, acpes: C.magenta, other: C.white, ipc: C.green, memory: C.yellow, lifecycle: "\x1b[38;5;82m", // Fluorescent Green routing: C.cyan, xems: "\x1b[38;5;165m", // Deep Purple console: C.white, }; // ───────────────────────────────────────────── // Default configuration // ───────────────────────────────────────────── // ───────────────────────────────────────────── // Logger // ───────────────────────────────────────────── class Logger { // ── Singleton ──────────────────────────── static instance; static getInstance(config) { if (!Logger.instance) { Logger.instance = new Logger(config); } else if (config) { Logger.instance.updateConfig(config); } return Logger.instance; } // ── Instance state ─────────────────────── config; buffer; flushTimer; isDisposed = false; palette = { ...DEFAULT_PALETTE_1.DEFAULT_PALETTE }; compColors = { ...DEFAULT_COMPONENT_COLORS, }; levelColors = {}; logQueue = []; isProcessingQueue = false; errorCount = 0; lastErrorTime = 0; suppressedComponents = new Set(); // ───────────────────────────────────────── // Constructor // ───────────────────────────────────────── constructor(config) { this.config = this.deepMerge(DEFAULT_LOGGER_CONFIG_1.DEFAULT_LOGGER_CONFIG, config ?? {}); this.applyColors(); this.initBuffer(); this.initErrorHandling(); } applyColors() { // Apply palette overrides if (this.config?.format?.palette) { this.palette = { ...DEFAULT_PALETTE_1.DEFAULT_PALETTE, ...this.config.format.palette, }; } else { this.palette = { ...DEFAULT_PALETTE_1.DEFAULT_PALETTE }; } // Apply component color overrides if (this.config?.format?.componentColors) { this.compColors = { ...DEFAULT_COMPONENT_COLORS, ...this.config.format.componentColors, }; } else { this.compColors = { ...DEFAULT_COMPONENT_COLORS }; } // Re-calculate level colors based on current palette const p = this.palette; this.levelColors = { error: p.brightRed ?? p.red, warn: p.yellow, debug: p.magenta, verbose: p.gray, }; } // ───────────────────────────────────────── // Public API — logging methods // ───────────────────────────────────────── error(component, message, ...args) { this.log("error", component, "errors", message, ...args); } warn(component, message, ...args) { this.log("warn", component, "warnings", message, ...args); } info(component, message, ...args) { this.log("info", component, undefined, message, ...args); } debug(component, message, ...args) { this.log("debug", component, "debug", message, ...args); } verbose(component, message, ...args) { this.log("verbose", component, "debug", message, ...args); } // ── Semantic helpers ───────────────────── startup(component, message, ...args) { this.log("info", component, "startup", message, ...args); } performance(component, message, ...args) { this.log("info", component, "performance", message, ...args); } hotReload(component, message, ...args) { this.log("info", component, "hotReload", message, ...args); } portSwitching(component, message, ...args) { this.log("info", component, "portSwitching", message, ...args); } success(component, message, ...args) { const colors = canColor() && this.config?.format?.colors !== false; const color = this.palette.brightGreen ?? this.palette.green; const greenMsg = colors ? `${color}${message}${this.palette.reset}` : message; this.log("info", component, "lifecycle", greenMsg, ...args); } securityWarning(message, ...args) { this.log("warn", "security", "warnings", message, ...args); } // ───────────────────────────────────────── // Public API — config / lifecycle // ───────────────────────────────────────── updateConfig(config) { const prev = this.config; this.config = this.deepMerge(this.config, config ?? {}); this.applyColors(); const bufferChanged = prev?.buffer?.enabled !== this.config?.buffer?.enabled || prev?.buffer?.autoFlush !== this.config?.buffer?.autoFlush; if (bufferChanged) this.initBuffer(); } getConfig() { return this.config; } getLevel() { return this.config?.level ?? "info"; } isEnabled() { return this.config?.enabled ?? false; } isComponentEnabled(component) { const cfg = this.config?.componentLevels?.[component]; if (cfg && typeof cfg === "object") return cfg.enabled !== false; return this.config?.components?.[component] !== false; } isTypeEnabled(type) { return this.config?.types?.[type] !== false; } getStats() { return { errorCount: this.errorCount, lastErrorTime: this.lastErrorTime, suppressedComponents: Array.from(this.suppressedComponents), bufferSize: this.buffer.entries.length, queueSize: this.logQueue.length, }; } clearSuppression() { this.suppressedComponents.clear(); this.errorCount = 0; this.lastErrorTime = 0; } flush() { if (this.buffer.entries.length === 0) return; const entries = this.buffer.entries.splice(0); this.buffer.lastFlush = Date.now(); entries.forEach((e) => this.writeEntry(e)); } dispose() { this.isDisposed = true; if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = undefined; } this.flush(); if (this.logQueue.length > 0) { this.logQueue.splice(0).forEach((e) => this.writeEntry(e)); } } /** Create a scoped child logger inheriting (and optionally overriding) config. */ child(_component, config) { return new Logger(this.deepMerge(this.config, config ?? {})); } // ───────────────────────────────────────── // Core — log pipeline // ───────────────────────────────────────── log(level, component, type, message, ...args) { try { if (!this.shouldLog(level, component, type, message)) return; const entry = { timestamp: new Date(), level, component, type, message, args, processId: this.readProcessId(), memory: this.readMemoryMB(), }; this.logQueue.push(entry); this.drainQueue(); } catch (err) { this.emergencyLog("error", "server", "Logging failed", err); } } drainQueue() { if (this.isProcessingQueue || this.logQueue.length === 0) return; this.isProcessingQueue = true; try { while (this.logQueue.length > 0) { const entry = this.logQueue.shift(); if (this.config?.buffer?.enabled) { this.buffer.entries.push(entry); if (this.buffer.entries.length >= this.buffer.maxSize) this.flush(); } else { this.writeEntry(entry); } } } catch (err) { this.emergencyLog("error", "server", "Queue drain failed", err); } finally { this.isProcessingQueue = false; } } // ───────────────────────────────────────── // Core — shouldLog gate // ───────────────────────────────────────── shouldLog(level, component, type, message) { if (this.isDisposed) return false; if (!this.config?.enabled) return false; if (this.config?.level === "silent") return false; if (level === "error" && this.shouldSuppressError(component)) return false; // Errors always pass (unless suppressed above) if (level === "error") return true; // Resolve effective level — component override takes precedence let effectiveLevel = this.config?.level ?? "info"; const compCfg = this.config?.componentLevels?.[component]; if (compCfg) { if (typeof compCfg === "string") { effectiveLevel = compCfg; } else if (typeof compCfg === "object") { if (compCfg.enabled === false) return false; if (compCfg.level) effectiveLevel = compCfg.level; if (type && compCfg.types?.[type] === false) return false; if (message && compCfg.suppressPatterns) { for (const pattern of compCfg.suppressPatterns) { const hit = typeof pattern === "string" ? message.includes(pattern) : pattern.test(message); if (hit) return false; } } } } // Level hierarchy gate if (LEVEL_RANK[level] > LEVEL_RANK[effectiveLevel]) return false; // Component toggle (legacy flat map) if (this.config?.components?.[component] === false) return false; // Type toggle if (type && this.config?.types?.[type] === false) return false; return true; } shouldSuppressError(component) { if (!this.config?.errorHandling?.suppressRepeatedErrors) return false; const now = Date.now(); const maxErrors = this.config.errorHandling?.maxErrorsPerMinute ?? 100; if (now - this.lastErrorTime > 60_000) { this.errorCount = 0; this.suppressedComponents.clear(); } this.errorCount++; this.lastErrorTime = now; if (this.errorCount > maxErrors) { this.suppressedComponents.add(component); return true; } return this.suppressedComponents.has(component); } // ───────────────────────────────────────── // Core — output // ───────────────────────────────────────── writeEntry(entry) { try { if (this.config?.customLogger) { this.config.customLogger(entry.level, entry.component, entry.message, ...entry.args); return; } const line = this.formatEntry(entry); switch (entry.level) { case "error": console.error(line, ...entry.args); break; case "warn": console.warn(line, ...entry.args); break; default: console.log(line, ...entry.args); break; } } catch (err) { this.emergencyLog("error", "server", "writeEntry failed", err); } } // ───────────────────────────────────────── // Core — formatting // ───────────────────────────────────────── /** * Produces a log line: * * {gray}HH:MM:SS.mmm{/} {color}[COMPONENT]{/} {color}message{/} * * Color priority: * 1. Level color — error=brightRed, warn=yellow, debug=magenta, verbose=gray * 2. Component color — each component has its own identity color (for info-class) * * Tag and message always share the same color so they read as one visual unit. */ formatEntry(entry) { const colors = canColor() && this.config?.format?.colors !== false; const compact = this.config?.format?.compact ?? false; const p = this.palette; // Active color: level wins for error/warn/debug/verbose; else component identity const lineColor = this.levelColors[entry.level] ?? this.compColors[entry.component] ?? p.white; // ── Timestamp (gray) ────────────────── let timestamp = ""; if (this.config?.format?.timestamps !== false) { const t = entry.timestamp; const hh = t.getHours().toString().padStart(2, "0"); const mm = t.getMinutes().toString().padStart(2, "0"); const ss = t.getSeconds().toString().padStart(2, "0"); const ms = t.getMilliseconds().toString().padStart(3, "0"); const raw = `${hh}:${mm}:${ss}.${ms}`; timestamp = colors ? `${p.gray}${raw}${p.reset}` : raw; } // ── [COMPONENT] tag ─────────────────── const label = this.componentLabel(entry.component); const tag = colors ? `${lineColor}${p.bold}[${label}]${p.reset}` : `[${label}]`; // ── Message (same color as tag) ─────── let msg = this.truncate(entry.message); if (colors) msg = `${lineColor}${msg}${p.reset}`; // ── Optional extras (pid, memory) ───── const extras = []; if (entry.processId !== undefined) { const s = `pid:${entry.processId}`; extras.push(colors ? `${p.gray}${s}${p.reset}` : s); } if (entry.memory !== undefined) { const s = `${entry.memory}MB`; extras.push(colors ? `${p.gray}${s}${p.reset}` : s); } // ── Compact mode ────────────────────── if (compact) { return [`[${label}]`, ...extras, msg].join(" "); } // ── Standard: timestamp [TAG] message ─ const right = [tag, ...extras, msg].join(" "); return timestamp ? `${timestamp} ${right}` : right; } // ───────────────────────────────────────── // Helpers // ───────────────────────────────────────── /** Human-readable uppercase label for a component. */ componentLabel(component) { const ALIASES = { server: "SYSTEM", cache: "SIMC", }; return (ALIASES[component] ?? component).toUpperCase(); } truncate(message) { const max = this.config?.format?.maxLineLength ?? 0; if (max === 0 || message.length <= max) return message; return `${message.substring(0, max - 3)}...`; } readMemoryMB() { if (!this.config?.format?.includeMemory) return undefined; try { return typeof process !== "undefined" ? Math.round(process.memoryUsage().heapUsed / 1_048_576) : undefined; } catch { return undefined; } } readProcessId() { if (!this.config?.format?.includeProcessId) return undefined; try { return typeof process !== "undefined" ? process.pid : undefined; } catch { return undefined; } } /** Bypass all filters — used only for logger-internal failures. */ emergencyLog(_level, component, message, ...args) { try { const p = this.palette; console.error(`${p.gray}${new Date().toISOString()}${p.reset} ${p.brightRed}${p.bold}[EMERGENCY:${component.toUpperCase()}]${p.reset} ${p.brightRed}${message}${p.reset}`, ...args); } catch { process?.stderr?.write(`[LOGGER_FAILURE] ${message}\n`); } } // ───────────────────────────────────────── // Init // ───────────────────────────────────────── initBuffer() { this.buffer = { entries: [], maxSize: this.config?.buffer?.maxSize ?? 1000, flushInterval: this.config?.buffer?.flushInterval ?? 5000, lastFlush: Date.now(), }; if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = undefined; } if (this.config?.buffer?.enabled && this.config?.buffer?.autoFlush) { this.flushTimer = setInterval(() => this.flush(), this.buffer.flushInterval); } } initErrorHandling() { // Reset per-minute error counter setInterval(() => { this.errorCount = 0; this.lastErrorTime = 0; const resetAfter = this.config?.errorHandling?.resetSuppressionAfter ?? 300_000; if (Date.now() - this.lastErrorTime > resetAfter) { this.suppressedComponents.clear(); } }, 60_000); if (typeof process === "undefined") return; process.on("uncaughtException", (error) => { this.emergencyLog("error", "server", "Uncaught Exception", error.message, error.stack); }); process.on("unhandledRejection", (reason, promise) => { this.emergencyLog("error", "server", "Unhandled Promise Rejection", reason, promise); }); } // ───────────────────────────────────────── // Utility // ───────────────────────────────────────── deepMerge(target, source) { // const result: any = { ...target }; // for (const key in source) { // const val = source[key]; // if ( // val !== null && // typeof val === "object" && // !Array.isArray(val) // ) { // result[key] = this.deepMerge(result[key] ?? {}, val as any); // } else { // result[key] = val; // } // } const result = (0, mergeWithDefaults_1.mergeWithDefaults)(target, source); return result; } } exports.Logger = Logger; // ───────────────────────────────────────────── // Module helpers // ───────────────────────────────────────────── /** Shared singleton — ready to use immediately. */ exports.logger = Logger.getInstance(); /** Configure and return the singleton logger. */ function initializeLogger(config) { return Logger.getInstance(config); } /** Flush + dispose the singleton — call on graceful shutdown. */ function cleanupLogger() { Logger.getInstance().dispose(); } //# sourceMappingURL=Logger.js.map