-
Notifications
You must be signed in to change notification settings - Fork 1
/
ConsoleOverrides.ts
145 lines (130 loc) · 5.07 KB
/
ConsoleOverrides.ts
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
export const LOG_LEVELS = ['debug', 'error', 'info', 'log', 'trace', 'warn'] as const;
export type LogLevel = typeof LOG_LEVELS[number];
export type LogPipeResult = unknown[] | { level: LogLevel, args: unknown[] };
export interface LogPipe<ResultType extends LogPipeResult = LogPipeResult> {
/**
* LogPipe is a functional interface that accepts a LogLevel and
* a list of arguments and transforms it into another list of arguments.
* The return is an array of transformed arguments.
* If the log pipe needs to change the log level, it should return an object with an updated `level` field.
*/
(level: LogLevel, ...args: any[]): ResultType;
onInstall?: () => void;
onUninstall?: () => void;
}
const consoleOverrides: Array<LogPipe> = [];
type ConsoleLogFn = (...args: any[]) => void;
const noop: ConsoleLogFn = () => {/* do nothing. */};
const originalConsole: Record<LogLevel, ConsoleLogFn> = {
debug: noop,
error: noop,
info: noop,
log: noop,
trace: noop,
warn: noop
};
/**
* Adds the pipe to the list of active console overrides.
* The pipes are called in the order they are installed.
*/
export function installConsoleOverride(pipe: LogPipe | Array<LogPipe>): void {
const pipes = Array.isArray(pipe) ? pipe : [pipe];
installConsoleOverrides(...pipes);
}
/**
* Adds the pipes to the list of active console overrides.
* The pipes are called in the order they are installed.
*/
export function installConsoleOverrides(...pipes: Array<LogPipe>): void {
initializeConsoleOverrideContextOnFirstUse();
for (const pipe of pipes) {
// Call pipe.onInstall() when pipe is installed first time.
if (!consoleOverrides.includes(pipe)) {
pipe.onInstall && pipe.onInstall();
}
consoleOverrides.push(pipe);
}
}
/** Removes the given pipe from the active console overrides. */
export function uninstallConsoleOverride(pipe: LogPipe | Array<LogPipe>): void {
const pipes = Array.isArray(pipe) ? pipe : [pipe];
uninstallConsoleOverrides(...pipes);
}
/** Removes the given pipes from the active console overrides. */
export function uninstallConsoleOverrides(...pipes: Array<LogPipe>): void {
for (const pipe of pipes) {
for (let pipeIndex = consoleOverrides.indexOf(pipe); pipeIndex >= 0; pipeIndex = consoleOverrides.indexOf(pipe)) {
const removedPipe = consoleOverrides.splice(pipeIndex, 1)[0];
// Call pipe.onUninstall() when the pipe is not present in the overrides anymore.
if (!consoleOverrides.includes(removedPipe)) {
removedPipe?.onUninstall && removedPipe.onUninstall();
}
}
}
destroyConsoleOverrideContextOnLastUse();
}
/** Uninstall all existing console overrides. */
export function uninstallAllConsoleOverrides(): void {
for (const pipe of [...consoleOverrides]) {
uninstallConsoleOverride(pipe);
}
}
/** Returns a list of all console overrides. */
export function getConsoleOverrides(): Array<LogPipe> {
return [...consoleOverrides];
}
/**
* Returns set of console.[log] methods captured during installation of the first pipe.
* If no pipe is installed, returns current console[log] methods.
*/
export function getOriginalConsoleMethods(): Record<LogLevel, ConsoleLogFn> {
if (originalConsole['debug'] !== noop) {
return {...originalConsole};
}
const result = {} as Record<LogLevel, ConsoleLogFn>;
for (const level of LOG_LEVELS) {
result[level] = console[level] as ConsoleLogFn;
}
return result;
}
function initializeConsoleOverrideContextOnFirstUse(): void {
if (originalConsole['debug'] !== noop) {
return; // Already installed.
}
for (const level of LOG_LEVELS) {
originalConsole[level] = console[level] as ConsoleLogFn;
console[level] = (...args: any[]): void => {
let resultLevel = level;
let resultArgs = args;
for (const pipe of consoleOverrides) {
const logPipeResult = pipe(resultLevel, ...resultArgs);
const isSuppressed = !logPipeResult
|| (Array.isArray(logPipeResult)
? (logPipeResult?.length ?? 0) === 0
: (logPipeResult.args?.length ?? 0) === 0);
if (isSuppressed) {
return; // Log is suppressed.
}
if (Array.isArray(logPipeResult)) {
resultArgs = logPipeResult;
continue;
}
resultLevel = logPipeResult.level;
resultArgs = logPipeResult.args;
}
originalConsole[resultLevel](...resultArgs);
};
}
}
function destroyConsoleOverrideContextOnLastUse(): void {
if (consoleOverrides.length > 0) {
return; // Too early to restore: there are overrides.
}
if (originalConsole['debug'] === noop) {
return; // Nothing to restore: not overridden.
}
for (const name of LOG_LEVELS) {
console[name] = originalConsole[name];
originalConsole[name] = noop;
}
}