import * as util from 'util'; import * as path from 'path'; import 'colorts/lib/string'; /* helper fns */ function getCallerFrame(shift: number = 0): NodeJS.CallSite | undefined { const prepareStackTrace = Error.prepareStackTrace; let loggedStack: NodeJS.CallSite[] | undefined; Error.prepareStackTrace = (_, stackTraces: NodeJS.CallSite[]) => { loggedStack = stackTraces; stackTraces.shift(); // discard curr frame return stackTraces; }; // eslint-disable-next-line @typescript-eslint/no-unused-expressions new Error().stack; Error.prepareStackTrace = prepareStackTrace; if (!loggedStack) return undefined; if (shift > 0) for (let i = 0; i < shift; i++) loggedStack.shift(); return loggedStack[1]; } /* logger */ const LOG_TYPES = { Info: '[Info]'.green, Verbose: '[Verbose]'.blue, Warning: '[Warning]'.yellow, Error: '[Error]'.red } as const; export class Logger { private readonly name: string; constructor(name: string) { this.name = name; } public info(fmt: string, ...args: unknown[]): void { this.log(LOG_TYPES.Info, fmt, args); } public verbose(fmt: string, ...args: unknown[]): void { this.log(LOG_TYPES.Verbose, fmt, args); } public warning(fmt: string, ...args: unknown[]): void { this.log(LOG_TYPES.Warning, fmt, args); } public error(fmt: string, ...args: unknown[]): void { this.log(LOG_TYPES.Error, fmt, args); } private log(type: string, message: string, args: unknown[]): void { const caller = getCallerFrame(1); if (!caller) { console.error('Failed to determine caller information'); return; } const timestamp = this.getFormattedTime(); const callerInfo = this.getCallerInfo(caller); const formattedMessage = util.format(message, ...args); console.log( `${timestamp} - ${callerInfo} [${this.name.magenta}] ${type} ${formattedMessage}` ); } private getFormattedTime(): string { const now = new Date(); return now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } private getCallerInfo(caller: NodeJS.CallSite): string { const functionName = caller.getFunctionName()?.replace(/\[.*\]/, '') || ''; const fileName = caller.getFileName() || 'unknown'; const relativePath = path.relative(process.cwd(), fileName); return `${functionName.cyan} @ ${relativePath.dim}`; } }