import { LoggerConfig } from '../types' import { SlackService } from './Slack' import { PerformanceService } from './performance' /** * Base logger class. Logging methods (`info`, `error`, `warn`, `log`, `debug`, `test`) are stubs * that must be overridden by the consuming app — calling them before providing an implementation * throws. Use {@link createLogger} to produce a concrete instance, then assign it to `logger`. * * `initialize` must be called once before `patchConsole` or any Slack/perf functionality works. */ export class Logger { static initialized = false private config!: LoggerConfig slack!: SlackService perf!: PerformanceService private isIgnored(args: unknown[]): boolean { if (!Logger.initialized) return false const ignoreLogs = this.config.Logger.ignoreLogs return typeof args[0] === 'string' && ignoreLogs.some(w => args.join(' ').includes(w)) } /** * Monkey-patches `console.log`, `console.warn`, and `console.error` so that any message * matching an entry in `config.Logger.ignoreLogs` is silently dropped. * * Call this after `initialize`. Patching before initialization has no effect because * `isIgnored` returns `false` until the config is loaded. */ patchConsole() { const consoles = ['log', 'warn', 'error'] as const consoles.forEach(level => { const consoleAny = console as unknown as Record void> const consoleRef = consoleAny[level].bind(console) consoleAny[level] = (...args: unknown[]) => { if (!this.isIgnored(args)) consoleRef(...args) } }) } /** * Bootstraps the logger with app-wide config. Safe to call multiple times — subsequent calls * are no-ops. Also instantiates `slack` and `perf` sub-services, so they are only available * after this method returns. */ initialize(config: T) { if (Logger.initialized) return this.config = config this.slack = new SlackService(config) this.perf = new PerformanceService(config) Logger.initialized = true } /** * Stub — must be overridden. Intended for temporary test logs that should never reach * production; implementations typically no-op in non-dev environments. */ test(...args: unknown[]){ throw new Error('Logger: implement the method "test"') } /** Stub — must be overridden. */ info(...args: unknown[]) { throw new Error('Logger: implement the method "info"') } /** Stub — must be overridden. */ error(...args: unknown[]) { throw new Error('Logger: implement the method "error"') } /** Stub — must be overridden. */ warn(...args: unknown[]) { throw new Error('Logger: implement the method "warn"') } /** Stub — must be overridden. */ log(...args: unknown[]) { throw new Error('Logger: implement the method "log"') } /** Stub — must be overridden. */ debug(...args: unknown[]) { throw new Error('Logger: implement the method "debug"') } } /** * Shared `Logger` instance. Logging methods throw until the consuming app replaces them via * {@link createLogger} and calls {@link Logger.initialize}. */ export const logger = new Logger()