-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconsole.ts
More file actions
138 lines (130 loc) · 4.76 KB
/
console.ts
File metadata and controls
138 lines (130 loc) · 4.76 KB
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
/**
* @file Lazy `Console` construction + dynamic prototype mirroring for `Logger`.
* Both helpers exist as free functions (rather than `Logger` static methods)
* because:
*
* - `constructConsole` caches the resolved `node:console` module so multiple
* `new Logger()` calls don't pay the require cost more than once. Caching
* at the function level is simpler than a class field that has to thread
* through all callers.
* - `ensurePrototypeInitialized` walks `globalConsole` once at first use and
* copies any console method that isn't already on `Logger.prototype`. Doing
* this lazily (rather than at module load) is what lets the logger be
* imported during early Node.js bootstrap before stdout is ready, which
* would otherwise crash with `ERR_CONSOLE_WRITABLE_STREAM`. Note on the
* apparent circular import with `core.ts`: `ensurePrototypeInitialized`
* mutates `Logger.prototype`, so it imports `Logger` from `./core`.
* `core.ts` calls `ensurePrototypeInitialized()` from inside a method body,
* so the import cycle never resolves at module-load time — by the time
* `ensurePrototypeInitialized` actually runs, both modules are fully
* evaluated. Same pattern as `fs/path-cache` ↔ `fs/_internal`.
*/
import process from 'node:process'
import {
ObjectDefineProperties,
ObjectEntries,
ObjectFromEntries,
} from '../primordials/object'
import { ReflectConstruct } from '../primordials/reflect'
import {
boundConsoleEntries,
consolePropAttributes,
globalConsole,
privateConsole,
privateConstructorArgs,
} from './_internal'
import { Logger } from './node'
import { getKGroupIndentationWidthSymbol } from './symbols'
let _Console: typeof import('node:console').Console | undefined
let _prototypeInitialized = false
/**
* Construct a new Console instance.
*/
/*@__NO_SIDE_EFFECTS__*/
export function constructConsole(...args: unknown[]) {
/* c8 ignore next - Lazy-init second-call branch; module-singleton. */
if (_Console === undefined) {
// Use non-'node:' prefixed require to avoid Webpack errors.
const nodeConsole = /*@__PURE__*/ require('node:console')
_Console = nodeConsole.Console
}
return ReflectConstruct(
_Console! as new (...args: unknown[]) => Console, // eslint-disable-line no-undef
args,
)
}
/**
* Lazily add dynamic console methods to Logger prototype.
*
* This is deferred until first access to avoid calling
* Object.entries(globalConsole) during early Node.js bootstrap before stdout is
* ready.
*/
export function ensurePrototypeInitialized() {
if (_prototypeInitialized) {
return
}
_prototypeInitialized = true
const entries: Array<[string | symbol, PropertyDescriptor]> = [
[
getKGroupIndentationWidthSymbol(),
{
...consolePropAttributes,
value: 2,
},
],
[
Symbol.toStringTag,
{
__proto__: null,
configurable: true,
value: 'logger',
} as PropertyDescriptor,
],
]
for (const { 0: key, 1: value } of ObjectEntries(globalConsole)) {
if (!(Logger.prototype as any)[key] && typeof value === 'function') {
// Dynamically name the log method without using Object.defineProperty.
const { [key]: func } = {
[key](this: Logger, ...args: unknown[]) {
// Access Console via WeakMap directly since private methods can't
// be called from dynamically created functions. con-undefined only
// fires if someone calls a dynamically added console method before
// any core logger method, which is rare.
/* c8 ignore start */
let con = privateConsole.get(this)
if (con === undefined) {
const ctorArgs = privateConstructorArgs.get(this) ?? []
privateConstructorArgs.delete(this)
if (ctorArgs.length) {
con = constructConsole(...ctorArgs)
} else {
con = constructConsole({
stdout: process.stdout,
stderr: process.stderr,
}) as typeof console & Record<string, unknown>
for (const { 0: k, 1: method } of boundConsoleEntries) {
con[k] = method
}
}
privateConsole.set(this, con)
}
/* c8 ignore stop */
const result = (con as any)[key](...args)
// Most Console methods return undefined; the `=== con` chain
// arm fires for builtin methods that return `this`.
/* c8 ignore next */
return result === undefined || result === con ? this : result
},
}
entries.push([
key,
{
...consolePropAttributes,
value: func,
},
])
}
}
ObjectDefineProperties(Logger.prototype, ObjectFromEntries(entries))
}