import { isDebug, isObject, type ITrace, type SideEffect, type Supplier, } from "@emprespresso/utils"; export interface ILogger { log: (...args: unknown[]) => void; debug: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; } export type ILoggerLevel = "UNKNOWN" | "INFO" | "WARN" | "DEBUG" | "ERROR"; const logLevelOrder: Array = ["DEBUG", "INFO", "WARN", "ERROR"]; const defaultAllowedLevels = () => [ "UNKNOWN", ...(isDebug() ? ["DEBUG"] : []), "INFO", "WARN", "ERROR", ] as Array; export const logWithLevel = ( logger: ILogger, level: ILoggerLevel, ): SideEffect => { switch (level) { case "UNKNOWN": case "INFO": return logger.log; case "DEBUG": return logger.debug; case "WARN": return logger.warn; case "ERROR": return logger.error; } }; export const LoggerImpl = console; export type LogTraceSupplier = string | Supplier | { level: ILoggerLevel; }; const foldTraces = (traces: Array) => { const { line, level } = traces.reduce( (acc: { line: string; level: number }, t) => { if (isObject(t) && "level" in t) { return { ...acc, level: Math.max(logLevelOrder.indexOf(t.level), acc.level), }; } const prefix = [ acc.line, typeof t === "function" ? t() : t, ].join(" "); return { ...acc, prefix }; }, { line: "", level: -1 }, ); return { line, level: logLevelOrder[level] ?? "UNKNOWN" }; }; const defaultTrace = () => `[${new Date().toISOString()}]`; export const LogTrace = ( logger: ILogger, traces: Array = [defaultTrace], allowedLevels: Supplier> = defaultAllowedLevels, defaultLevel: ILoggerLevel = "INFO", ): ITrace => { return { addTrace: (trace: LogTraceSupplier) => LogTrace(logger, traces.concat(trace)), trace: (trace: LogTraceSupplier) => { const { line, level: _level } = foldTraces(traces.concat(trace)); if (!allowedLevels().includes(_level)) return; const level = _level === "UNKNOWN" ? defaultLevel : _level; logWithLevel(logger, level)(`[${level}]${line}`); }, }; };