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