import Debug from "debug"; import { setDebug } from "./debug"; const loggers = { debug: Debug("magicbell:debug"), info: Debug("magicbell:info"), error: Debug("magicbell:error") }; export type LogLevel = "debug" | "info" | "error"; export type LogEvent = { level: LogLevel; message: string; data?: unknown; time: number; }; type LogListener = (e: LogEvent) => void; const listeners = new Set(); function emit(level: LogLevel, message: string, data?: unknown) { const evt: LogEvent = { level, message, data, time: Date.now() }; for (const l of listeners) { try { l(evt); } catch { // ignore listener errors } } } function makeLogger(level: LogLevel) { const fn = loggers[level]; return (message: string, data?: unknown) => { fn(message, data); emit(level, message, data); }; } function wrap any>(level: Exclude, message: string, handler: T) { return function (...args: Parameters): ReturnType { const logger = log[level]; const start = Date.now(); // only log the arguments that are consumed, vs provided. const input = args.slice(0, handler.length); logger(message, { args: input }); const logResult = (result: any, duration: number) => { logger(message, { duration: `${duration} ms`, args: input, result }); return result; }; const logError = (err: any, duration: number): never => { logger(message, { duration: `${duration} ms`, args: input, error: err }); throw err; }; try { const result = handler(...args); if (result instanceof Promise) { return result.then(res => logResult(res, Date.now() - start)).catch(err => logError(err, Date.now() - start)) as ReturnType; } else { return logResult(result, Date.now() - start); } } catch (err) { return logError(err, Date.now() - start) as ReturnType; } }; } export const log = { debug: makeLogger("debug"), info: makeLogger("info"), error: makeLogger("error"), wrap }; export function onLog(listener: LogListener): () => void { listeners.add(listener); return () => listeners.delete(listener); } setDebug("onLog", onLog)