const SENSITIVE = /token|key|secret|password|authorization|credential/i /** Redact values whose field names look sensitive; used by the structured logger. */ export function redactFields(fields: Record): Record { const out: Record = {} for (const [k, v] of Object.entries(fields)) { out[k] = SENSITIVE.test(k) ? '[redacted]' : v } return out } export type LogLevel = 'debug' | 'info' | 'warn' | 'error' export interface Logger { debug(message: string, fields?: Record): void info(message: string, fields?: Record): void warn(message: string, fields?: Record): void error(message: string, fields?: Record): void } export interface LogContext { component: string revision?: string } /** * Structured JSON logger on stdout/stderr. * Spec: operational logs must never contain secrets, chat bodies, or raw tool output; * callers pass only metadata, and sensitive-looking keys are masked defensively. */ export function createLogger(ctx: LogContext): Logger { const write = (level: LogLevel, message: string, fields?: Record) => { const entry = { ts: new Date().toISOString(), level, component: ctx.component, ...(ctx.revision ? { revision: ctx.revision } : {}), message, ...(fields ? redactFields(fields) : {}), } const line = JSON.stringify(entry) if (level === 'error') process.stderr.write(`${line}\n`) else process.stdout.write(`${line}\n`) } return { debug: (m, f) => write('debug', m, f), info: (m, f) => write('info', m, f), warn: (m, f) => write('warn', m, f), error: (m, f) => write('error', m, f), } }