// Safe logging utility (stderr only, JSON Lines). import { redact } from './redact.js'; const LEVELS = { silent: 0, info: 10, warn: 20, error: 30 }; const SECRET_KEY_PATTERN = /(api[_-]?key|token|secret|password)/i; function sanitizeMetadata(meta) { if (!meta) return {}; const filtered = {}; for (const [key, value] of Object.entries(meta)) { if (typeof value === 'string' && SECRET_KEY_PATTERN.test(key.toLowerCase())) { filtered[key] = '[REDACTED]'; } else if (typeof value === 'string' && /^Bearer\s+/i.test(value)) { // Preserve "Bearer " prefix, redact the token portion const cleaned = value.replace(/(Bearer\s+).+/, '$1[REDACTED]'); filtered[key] = cleaned; } else { filtered[key] = value; } } const jsonStr = JSON.stringify(filtered); const redactedStr = redact(jsonStr); try { return JSON.parse(redactedStr); } catch { return filtered; } } export function createLogger(level) { if (!(level in LEVELS)) { throw new Error(`Invalid log level: ${level}`); } const currentLevel = LEVELS[level]; function write(eventLevel, message, metadata) { if (LEVELS[eventLevel] < currentLevel && currentLevel !== LEVELS.silent) return; if (currentLevel === LEVELS.silent) return; const meta = sanitizeMetadata(metadata); const entry = JSON.stringify({ level: eventLevel, ts: new Date().toISOString(), msg: message, meta, }); process.stderr.write(entry + '\n'); } return { info: (message, metadata) => write('info', message, metadata), warn: (message, metadata) => write('warn', message, metadata), error: (message, metadata) => write('error', message, metadata), }; }