xypriss-security
Version:
Advanced High-Performance Security Framework. Military-grade encryption, post-quantum resilience, and fortified data structures.
585 lines • 24 kB
JavaScript
"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