export type LogLevel = "error" | "warn" | "info" | "verbose" | "debug"; const LEVEL_SEVERITY: Record = { error: 0, warn: 1, info: 2, verbose: 3, debug: 4, }; const DEFAULT_LEVEL: LogLevel = "info"; export function resolveLogLevel(raw: string | undefined): LogLevel { const candidate = raw?.trim().toLowerCase(); if (candidate && candidate in LEVEL_SEVERITY) { return candidate as LogLevel; } return DEFAULT_LEVEL; } export function toErrorMeta(error: unknown): Record { if (error instanceof Error) { return { errorName: error.name, errorMessage: error.message, stack: error.stack, }; } return { error: typeof error === "string" ? error : JSON.stringify(error) }; } export interface Logger { error(message: string, meta?: Record): void; warn(message: string, meta?: Record): void; info(message: string, meta?: Record): void; verbose(message: string, meta?: Record): void; debug(message: string, meta?: Record): void; } export function createLogger( scope: string, options?: { level?: LogLevel } ): Logger { const threshold = options?.level ?? resolveLogLevel(process.env.LOG_LEVEL); const write = (level: LogLevel, message: string, meta?: Record) => { if (LEVEL_SEVERITY[level] > LEVEL_SEVERITY[threshold]) { return; } const line = JSON.stringify({ timestamp: new Date().toISOString(), level, scope, message, ...meta, }); if (level === "error" || level === "warn") { console.error(line); } else { console.log(line); } }; return { error: (message, meta) => write("error", message, meta), warn: (message, meta) => write("warn", message, meta), info: (message, meta) => write("info", message, meta), verbose: (message, meta) => write("verbose", message, meta), debug: (message, meta) => write("debug", message, meta), }; }