import { appendFileSync } from "node:fs"; import { format } from "node:util"; const colors = { reset: "\x1b[0m", gray: "\x1b[90m", white: "\x1b[37m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", bold: "\x1b[1m", } as const; const levelColors = { debug: colors.gray, info: colors.green, warn: colors.yellow, error: colors.red, } as const; const formatTime = (date: Date) => Intl.DateTimeFormat("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit", fractionalSecondDigits: 3, hourCycle: "h23", }).format(date); const formatPrefixNoColor = (level: string, name: string, time: Date) => { const levelFormatted = level.toUpperCase().padStart(5); const timestamp = formatTime(time); return `[${timestamp}] ${levelFormatted} (${name})`; }; const formatPrefixWithColor = (level: string, name: string, time: Date) => { const levelFormatted = level.toUpperCase().padStart(5); const timestamp = formatTime(time); const levelTint = levelColors[level as keyof typeof levelColors] ?? ""; return `${colors.gray}[${timestamp}]${colors.reset} ${levelTint}${levelFormatted}${colors.reset} (${name})`; }; type LoggerConfig = { name: string; stdout: boolean; logFile?: string; }; type LoggerInput = { name: string; stdout?: boolean; logFile?: string; }; const writeLogFile = (logFile: string | undefined, line: string) => { if (!logFile) return; appendFileSync(logFile, `${line}\n`); }; const logLine = (config: LoggerConfig, level: "debug" | "info" | "warn" | "error", args: any[]) => { const message = args.length > 0 ? format(...args) : ""; const time = new Date(); const plainPrefix = formatPrefixNoColor(level, config.name, time); const plainLine = `${plainPrefix} ${message}`; writeLogFile(config.logFile, plainLine); if (!config.stdout) return; const coloredPrefix = formatPrefixWithColor(level, config.name, time); const coloredLine = `${coloredPrefix} ${message}`; switch (level) { case "error": console.error(coloredLine); break; case "warn": console.warn(coloredLine); break; case "info": console.info(coloredLine); break; default: console.debug(coloredLine); break; } }; export const logger = (input: LoggerInput) => { const config: LoggerConfig = { stdout: true, ...input, }; return { info: (...args: any[]) => logLine(config, "info", args), error: (...args: any[]) => logLine(config, "error", args), warn: (...args: any[]) => logLine(config, "warn", args), debug: (...args: any[]) => logLine(config, "debug", args), child: (suffix: string, overrides: Partial> = {}) => logger({ ...config, ...overrides, name: `${config.name}:${suffix}`, }), }; }; export type Logger = ReturnType;