summaryrefslogtreecommitdiff
path: root/u/trace/logger.ts
blob: a5739c88109e08f479725d9d4c7c050592d5cd9e (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
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;
}
export enum LogLevel {
  UNKNOWN = "UNKNOWN",
  INFO = "INFO",
  WARN = "WARN",
  DEBUG = "DEBUG",
  ERROR = "ERROR",
}
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,
  ] 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;
  }
};

export type LogTraceSupplier = ITraceWith<Supplier<string>>;

const defaultTrace = () => `[${new Date().toISOString()}]`;
export const LoggerImpl = console;
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 { line, level: _level } = this.foldTraces(this.traces.concat(trace));
    if (!this.allowedLevels().includes(_level)) return;

    const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level;
    logWithLevel(this.logger, level)(`[${level}]${line}`);
  }

  private foldTraces(traces: Array<LogTraceSupplier>) {
    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 };
  }
}