62 lines
1.7 KiB
JavaScript
62 lines
1.7 KiB
JavaScript
// 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),
|
|
};
|
|
}
|