import { isDebug, type ITrace, type ITraceWith, 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; sys: (...args: unknown[]) => void; } export enum LogLevel { UNKNOWN = "UNKNOWN", INFO = "INFO", WARN = "WARN", DEBUG = "DEBUG", ERROR = "ERROR", SYS = "SYS", } 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, LogLevel.SYS, ] 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; case LogLevel.SYS: return logger.sys; } }; export type LogTraceSupplier = ITraceWith>; const defaultTrace = () => `[${new Date().toISOString()}]`; export const LoggerImpl = { log: console.log, debug: console.debug, warn: console.warn, error: console.error, sys: console.log, }; export class LogTrace implements ITrace { constructor( private readonly logger: ILogger = LoggerImpl, private readonly traces: Array = [defaultTrace], private readonly allowedLevels: Supplier< Array > = defaultAllowedLevels, private readonly defaultLevel: LogLevel = LogLevel.INFO, ) {} public addTrace(trace: LogTraceSupplier): ITrace { return new LogTrace( this.logger, this.traces.concat(trace), this.allowedLevels, this.defaultLevel, ); } public trace(trace: LogTraceSupplier) { const { traces, level: _level } = this.foldTraces( this.traces.concat(trace), ); if (!this.allowedLevels().includes(_level)) return; const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level; const line = { level, message: traces.at(-1), traces: traces.slice(0, -1) }; logWithLevel( this.logger, level, )(line); } private foldTraces(_traces: Array) { const _logTraces = _traces.map((trace) => typeof trace === "function" ? trace() : trace, ); const _level = _logTraces .filter((trace) => isLogLevel(trace)) .reduce((acc, level) => Math.max(logLevelOrder.indexOf(level), acc), -1); const level = logLevelOrder[_level] ?? LogLevel.UNKNOWN; const traces = _logTraces.filter((trace) => !isLogLevel(trace)); return { level, traces, }; } }