/** * dlog.ts - Developer Logging Utility for Node.js/TypeScript Projects * * Provides a configurable, developer-friendly logging namespace with support for: * - Multiple log levels (debug, info, success, error, exception) * - Unicode icons for log levels * - Automatic caller function name detection * - Redaction of sensitive fields (e.g., keys, tokens) in objects * - Pretty-printing of objects and arrays * - Full stack trace logging for exceptions * - Configurable development mode and PII logging * * Usage: * import { dlog } from './dlog'; * dlog.setConfig({ isDev: true }); * dlog.d("Debug message", { some: "object" }); * dlog.e("Error message"); * dlog.ex(new Error("Something went wrong")); */ export namespace dlog { interface DlogConfig { isDev?: boolean; enablePII?: boolean; ignoreAllLogging?: boolean; } // This will store any data other than debug const logs_history: string[] = []; let config: DlogConfig = { isDev: true, enablePII: false }; // Unicode icons for log levels const ICONS = { DEBUG: "⚪️", // grey circle INFO: "ℹ️", SUCCESS: "✅", ERROR: "🛑", EXCEPTION: "⚠️", }; /** * Sets the logging configuration. * @param cfg Configuration object (e.g., { isDev: true, enablePII: false }) */ export function setConfig(cfg: DlogConfig) { config = { ...config, ...cfg }; } /** * Converts any input to a string, with a length limit. * Redacts values for keys containing "token" or "key". */ function convertMsg(input: unknown, limit = 5000): string { function redact(obj: any): any { if (obj === null || obj === undefined) return obj; if (Array.isArray(obj)) return obj.map(redact); if (typeof obj === "object") { const result: Record = {}; for (const [k, v] of Object.entries(obj)) { if ( typeof k === "string" && (k.toLowerCase().includes("token") || k.toLowerCase().includes("key")) ) { result[k] = "****"; } else { result[k] = redact(v); } } return result; } return obj; } if (input === null) return "null"; if (input === undefined) return "undefined"; let final: string; if (typeof input === "object") { try { final = JSON.stringify(redact(input), null, 1); } catch { final = "[Unserializable Object]"; } } else { final = String(input); } return ( final.substring(0, limit) + (final.length > limit ? "(truncated)" : "") ); } /** * Gets the name of the caller function from the stack trace. */ function getCallerFunctionName(): string { try { const err = new Error(); const stack = err.stack?.split("\n"); // stack[0] is 'Error', stack[1] is this function, stack[2] is the caller if (stack && stack.length >= 5) { const match = stack[4].match(/\((.*):(\d+):(\d+)\)$/); if (match) { const filePath = match[1]; const line = match[2]; // Optionally, you can trim the file path to just the filename: const fileName = filePath.split(/[\\/]/).pop(); return `${fileName}:${line}`; } } } catch (e) { // pass } return "anonymous"; } /** * Internal logging function. * Formats and prints the log message based on the log level. * If msg2 is an object or array, prints it as JSON. */ function log(level: keyof typeof ICONS, msg: unknown, msg2?: unknown) { if (config.ignoreAllLogging) return; if (!config.isDev && level !== "ERROR" && level !== "EXCEPTION") return; const now = new Date().toLocaleString(); const caller = getCallerFunctionName(); const icon = ICONS[level] || ""; let msg2Str = ""; if (msg2 !== undefined) { if (typeof msg2 === "object") { msg2Str = " " + convertMsg(msg2); } else { msg2Str = " " + String(msg2); } } const message = `[${icon}${level}] [${now}][${caller}] ${convertMsg( msg )}${msg2Str}`; if (level != "DEBUG") { logs_history.push(message); } console.log(message); } /** * Logs a debug message. */ export function d(msg: unknown, msg2?: unknown) { log("DEBUG", msg, msg2); } /** * Logs an info message. */ export function i(msg: unknown, msg2?: unknown) { log("INFO", msg, msg2); } /** * Logs a success message. */ export function s(msg: unknown, msg2?: unknown) { log("SUCCESS", msg, msg2); } /** * Logs an error message. */ export function e(msg: unknown, msg2?: unknown) { log("ERROR", msg, msg2); } /** * Logs an exception with callstack and unicode warning. * Prints the full stack trace. */ export function ex(error: Error, ignore = true, msg: string = "") { if (config.ignoreAllLogging) return; if (!config.isDev) return; const now = new Date().toLocaleString(); const caller = getCallerFunctionName(); const icon = ICONS.EXCEPTION; const stack = !ignore && error.stack ? `\n${error.stack}` : ""; let message = `[${now}][${icon} EXCEPTION][${caller}] ${msg ?? ""} ${ error.message }${stack}`; console.log(message); logs_history.push(message); } /** * Returns the log history and clears it. */ export function getLogHistoryAndFlash(): string[] { const history = [...logs_history]; logs_history.length = 0; return history; } }