type Level = "debug" | "info" | "warn" | "error"; const PRIORITY: Record = { debug: 0, info: 1, warn: 2, error: 3, }; function getMinLevel(): Level { const env = process.env.LOG_LEVEL as Level | undefined; if (env && env in PRIORITY) return env; const appEnv = process.env.APP_ENV; return appEnv === "production" ? "info" : "debug"; } function shouldLog(level: Level): boolean { return PRIORITY[level] >= PRIORITY[getMinLevel()]; } // TZ 환경변수 기준 로컬 시간 (toISOString은 항상 UTC라 사용하지 않음) export function timestamp(): string { const d = new Date(); const pad = (n: number, w = 2) => String(n).padStart(w, "0"); const offMin = -d.getTimezoneOffset(); const sign = offMin >= 0 ? "+" : "-"; const abs = Math.abs(offMin); return ( `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` + `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` + `.${pad(d.getMilliseconds(), 3)}` + `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}` ); } function write( level: Level, ctx: string, message: string, data?: Record ): void { if (!shouldLog(level)) return; const ts = timestamp(); const tag = level.toUpperCase().padEnd(5); const line = data ? `[${ts}] [${tag}] [${ctx}] ${message} ${JSON.stringify(data)}` : `[${ts}] [${tag}] [${ctx}] ${message}`; if (level === "warn" || level === "error") { console.error(line); } else { console.log(line); } } export const logger = { debug: (ctx: string, message: string, data?: Record) => write("debug", ctx, message, data), info: (ctx: string, message: string, data?: Record) => write("info", ctx, message, data), warn: (ctx: string, message: string, data?: Record) => write("warn", ctx, message, data), error: (ctx: string, message: string, data?: Record) => write("error", ctx, message, data), isEnabled: (level: Level): boolean => shouldLog(level), };