summaryrefslogtreecommitdiff
path: root/u/trace/logger.ts
blob: 91432fe920f58236a0961bb73905c3447594e37d (plain)
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
121
122
123
124
125
126
import { isDebug, type ITrace, type ITraceWith, type Supplier } from '@emprespresso/pengueno';

export type LogTraceSupplier = ITraceWith<Supplier<string> | Error>;
const defaultTrace = () => `TimeStamp = ${new Date().toISOString()}`;
export class LogTrace implements ITrace<LogTraceSupplier> {
    constructor(
        private readonly logger: ILogger = new LoggerImpl(),
        private readonly traces: Array<LogTraceSupplier> = [defaultTrace],
        private readonly defaultLevel: LogLevel = LogLevel.INFO,
        private readonly allowedLevels: Supplier<Array<LogLevel>> = defaultAllowedLevels,
    ) {}

    public addTrace(trace: LogTraceSupplier): ITrace<LogTraceSupplier> {
        return new LogTrace(this.logger, this.traces.concat(trace), this.defaultLevel, this.allowedLevels);
    }

    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;
        this.logger.log(level, ...traces);
    }

    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))
            .map((trace) => {
                if (typeof trace === 'object') {
                    return `TracedException.Name = ${trace.name}, TracedException.Message = ${trace.message}, TracedException.Stack = ${trace.stack}`;
                }
                return trace;
            });
        return {
            level,
            traces,
        };
    }
}

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, LogLevel.SYS];
export const isLogLevel = (l: unknown): l is LogLevel =>
    typeof l === 'string' && logLevelOrder.some((level) => level === l);

const defaultAllowedLevels = () =>
    [
        LogLevel.UNKNOWN,
        ...(isDebug() ? [LogLevel.DEBUG] : []),
        LogLevel.INFO,
        LogLevel.WARN,
        LogLevel.ERROR,
        LogLevel.SYS,
    ] as Array<LogLevel>;

export interface ILogger {
    readonly log: (level: LogLevel, ...args: string[]) => void;
}
class LoggerImpl implements ILogger {
    private readonly textEncoder = new TextEncoder();

    public log(level: LogLevel, ...trace: string[]) {
        const message = JSON.stringify(
            {
                level,
                trace,
            },
            null,
            4,
        );
        const styled = `${this.getStyle(level)}${message}${ANSI.RESET}\n`;
        this.getStream(level)(this.textEncoder.encode(styled));
    }

    private getStream(level: LogLevel) {
        if (level === LogLevel.ERROR) {
            return console.error;
        }
        return console.log;
    }

    private getStyle(level: LogLevel) {
        switch (level) {
            case LogLevel.UNKNOWN:
            case LogLevel.INFO:
                return `${ANSI.MAGENTA}`;
            case LogLevel.DEBUG:
                return `${ANSI.CYAN}`;
            case LogLevel.WARN:
                return `${ANSI.BRIGHT_YELLOW}`;
            case LogLevel.ERROR:
                return `${ANSI.BRIGHT_RED}`;
            case LogLevel.SYS:
                return `${ANSI.DIM}${ANSI.BLUE}`;
        }
    }
}

export const ANSI = {
    RESET: '\x1b[0m',
    BOLD: '\x1b[1m',
    DIM: '\x1b[2m',
    RED: '\x1b[31m',
    GREEN: '\x1b[32m',
    YELLOW: '\x1b[33m',
    BLUE: '\x1b[34m',
    MAGENTA: '\x1b[35m',
    CYAN: '\x1b[36m',
    WHITE: '\x1b[37m',
    BRIGHT_RED: '\x1b[91m',
    BRIGHT_YELLOW: '\x1b[93m',
    GRAY: '\x1b[90m',
};