Challenge 2: Redact Sensitive Fields — Possible Solution ==================================================================== interface LogFields { [key: string]: string | number | boolean | undefined; } const SENSITIVE_KEYS = new Set(["password", "token", "secret"]); function isSensitiveKey(key: string): boolean { const lower = key.toLowerCase(); return [...SENSITIVE_KEYS].some((sensitive) => lower.includes(sensitive)); } function redact(fields: LogFields): LogFields { const safe: LogFields = {}; for (const [key, value] of Object.entries(fields)) { safe[key] = isSensitiveKey(key) ? "[REDACTED]" : value; } return safe; } // --- Wired into the logger from Challenge 1 --- type LogLevel = "debug" | "info" | "warn" | "error"; interface Logger { debug(event: string, fields?: LogFields): void; info(event: string, fields?: LogFields): void; warn(event: string, fields?: LogFields): void; error(event: string, fields?: LogFields): void; } class ConsoleLogger implements Logger { private write(level: LogLevel, event: string, fields: LogFields = {}): void { const line = JSON.stringify({ level, event, ...redact(fields) }); console.log(line); } debug(event: string, fields?: LogFields): void { this.write("debug", event, fields); } info(event: string, fields?: LogFields): void { this.write("info", event, fields); } warn(event: string, fields?: LogFields): void { this.write("warn", event, fields); } error(event: string, fields?: LogFields): void { this.write("error", event, fields); } } // --- Usage --- const logger: Logger = new ConsoleLogger(); logger.info("user.login", { userId: "u1", password: "hunter2", sessionToken: "abc123", }); // {"level":"info","event":"user.login","userId":"u1","password":"[REDACTED]","sessionToken":"[REDACTED]"} WHY THIS WORKS -------------- - isSensitiveKey() uses .includes() rather than an exact match, so "sessionToken", "apiToken", and "token" are all caught by the single "token" entry in SENSITIVE_KEYS — matching real-world field naming, which is rarely an exact match to a fixed list. - redact() builds a brand-new object rather than mutating the input, so the original fields object (which the caller might still use elsewhere) is never silently altered. - Wiring redact() into the single private write() method in ConsoleLogger means every log level (debug/info/warn/error) gets the protection automatically — there's no call site where a developer could forget it.