185 error spans on 0 routes in the last 7 days.
const REDACTED_KEYS = new Set([
'password',
'token',
'accessToken',
'refreshToken',
'authorization',
'cookie',
'apiKey',
'secret',
'sessionToken',
]);
const MAX_STRING_LEN = 2000;
const MAX_DEPTH = 8;
const MAX_ARRAY = 50;
export interface RedactOptions {
captureBodies?: boolean;
}
export function redact(value: unknown, opts: RedactOptions = {}): unknown {
const bodyKeys = opts.captureBodies
? new Set<string>()
: new Set(['body', 'htmlBody', 'rawBody', 'textBody']);
return walk(value, 0, bodyKeys);
}
function walk(v: unknown, depth: number, bodyKeys: Set<string>): unknown {
if (depth > MAX_DEPTH) return '[max-depth]';
if (v == null) return v;
if (typeof v === 'string') {
return v.length > MAX_STRING_LEN ? `${v.slice(0, MAX_STRING_LEN)}…[+${v.length - MAX_STRING_LEN}]` : v;
}
if (typeof v === 'number' || typeof v === 'boolean') return v;
if (typeof v === 'bigint') return v.toString();
if (typeof v === 'function') return '[function]';
if (v instanceof Date) return v.toISOString();
if (v instanceof Error) return { name: v.name, message: v.message, stack: v.stack };
if (v instanceof Map) return walk(Object.fromEntries(v), depth + 1, bodyKeys);
if (v instanceof Set) return walk(Array.from(v), depth + 1, bodyKeys);
if (Array.isArray(v)) {
const out = v.slice(0, MAX_ARRAY).map((x) => walk(x, depth + 1, bodyKeys));
if (v.length > MAX_ARRAY) out.push(`…[+${v.length - MAX_ARRAY}]` as unknown);
return out;
}
if (typeof v === 'object') {
const out: Record<string, unknown> = {};
for (const [k, val] of Object.entries(v)) {
if (REDACTED_KEYS.has(k)) {
out[k] = '[redacted]';
} else if (bodyKeys.has(k)) {
const s = typeof val === 'string' ? val : '';
out[k] = `[body redacted, ${s.length} chars]`;
} else {
out[k] = walk(val, depth + 1, bodyKeys);
}
}
return out;
}
return String(v);
}