export type LogLevel = "debug" | "info" | "warn" | "error"; export interface LogRecord { level: LogLevel; msg: string; at: string; fields: Record; } export type LogSink = (record: LogRecord) => void; export interface TaskLogger { debug(msg: string, fields?: Record): void; info(msg: string, fields?: Record): void; warn(msg: string, fields?: Record): void; error(msg: string, fields?: Record): void; child(fields: Record): TaskLogger; } const SENSITIVE_KEY = /pass(word|key)?|secret|token|cookie|authorization|api[-_]?key|credential|session/i; // URL 查询参数里的凭证(passkey、token、apikey、sign 等) const SENSITIVE_QUERY = /([?&](?:passkey|token|apikey|api_key|key|sign|secret|auth)=)[^&#\s]+/gi; const REDACTED = "***"; /** 深拷贝并脱敏:敏感键名整体替换,字符串里的凭证型 URL 参数替换 */ export function redact(value: unknown, depth = 0): unknown { if (depth > 8) return "[depth]"; if (typeof value === "string") return value.replace(SENSITIVE_QUERY, `$1${REDACTED}`); if (Array.isArray(value)) return value.map((v) => redact(v, depth + 1)); if (value instanceof Error) return { name: value.name, message: redact(value.message, depth + 1) }; if (value && typeof value === "object") { const out: Record = {}; for (const [k, v] of Object.entries(value)) out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redact(v, depth + 1); return out; } return value; } export const consoleSink: LogSink = (r) => { const line = JSON.stringify(r); if (r.level === "error" || r.level === "warn") console.error(line); else console.log(line); }; export const silentSink: LogSink = () => {}; export function createLogger(sink: LogSink = consoleSink, base: Record = {}): TaskLogger { const emit = (level: LogLevel) => (msg: string, fields: Record = {}) => sink({ level, msg: redact(msg) as string, at: new Date().toISOString(), fields: redact({ ...base, ...fields }) as Record, }); return { debug: emit("debug"), info: emit("info"), warn: emit("warn"), error: emit("error"), child: (fields) => createLogger(sink, { ...base, ...fields }), }; }