Challenge 1: Build a Typed Logger Interface — Possible Solution ==================================================================== type LogLevel = "debug" | "info" | "warn" | "error"; interface LogFields { [key: string]: string | number | boolean | undefined; } 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, ...fields, timestamp: new Date().toISOString() }); 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("server.started", { port: 3000 }); // {"level":"info","event":"server.started","port":3000,"timestamp":"2026-07-04T12:00:00.000Z"} logger.error("db.connection_failed", { host: "db.internal", retryCount: 3 }); // {"level":"error","event":"db.connection_failed","host":"db.internal","retryCount":3,"timestamp":"..."} WHY THIS WORKS -------------- - Logger is an interface, so any class that depends on it (an OrderService, a request handler) can accept a Logger without knowing or caring that ConsoleLogger is the concrete implementation — swap in a PinoLogger later with zero changes to calling code, exactly like Database/Mailer in Chapter 1. - A single private `write()` method centralizes the actual formatting logic, so debug/info/warn/error only need to supply the level. - LogFields being an index signature of string | number | boolean | undefined means callers can pass any flat object of primitives without TypeScript complaining, while still catching a mistake like passing a nested object or a function as a field value.