1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
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> = [
LogLevel.DEBUG,
LogLevel.INFO,
LogLevel.WARN,
LogLevel.ERROR,
];
export const isLogLevel = (l: string): l is LogLevel =>
logLevelOrder.some((level) => <string>level === l);
const defaultAllowedLevels = () =>
[
LogLevel.UNKNOWN,
...(isDebug() ? [LogLevel.DEBUG] : []),
LogLevel.INFO,
LogLevel.WARN,
LogLevel.ERROR,
LogLevel.SYS,
] as Array<LogLevel>;
export const logWithLevel = (
logger: ILogger,
level: LogLevel,
): SideEffect<unknown> => {
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<Supplier<string>>;
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<LogTraceSupplier> {
constructor(
private readonly logger: ILogger = LoggerImpl,
private readonly traces: Array<LogTraceSupplier> = [defaultTrace],
private readonly allowedLevels: Supplier<
Array<LogLevel>
> = defaultAllowedLevels,
private readonly defaultLevel: LogLevel = LogLevel.INFO,
) {}
public addTrace(trace: LogTraceSupplier): ITrace<LogTraceSupplier> {
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<LogTraceSupplier>) {
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,
};
}
}
|