@@ -214,7 +214,27 @@ const consolePropAttributes = {
214214 configurable : true ,
215215}
216216const maxIndentation = 1000
217+
218+ /**
219+ * WeakMap storing the Console instance for each Logger.
220+ *
221+ * Console creation is lazy - deferred until first logging method call.
222+ * This allows logger to be imported during early Node.js bootstrap before
223+ * stdout is ready, avoiding ERR_CONSOLE_WRITABLE_STREAM errors.
224+ */
217225const privateConsole = new WeakMap ( )
226+
227+ /**
228+ * WeakMap storing constructor arguments for lazy Console initialization.
229+ *
230+ * WeakMap is required instead of a private field (#constructorArgs) because:
231+ * 1. Private fields can't be accessed from dynamically created functions
232+ * 2. Logger adds console methods dynamically to its prototype (lines 1560+)
233+ * 3. These dynamic methods need constructor args for lazy initialization
234+ * 4. WeakMap allows both regular methods and dynamic functions to access args
235+ *
236+ * The args are deleted from the WeakMap after Console is created (memory cleanup).
237+ */
218238const privateConstructorArgs = new WeakMap ( )
219239
220240const consoleSymbols = Object . getOwnPropertySymbols ( globalConsole )
@@ -319,7 +339,6 @@ export class Logger {
319339 #stderrLastWasBlank = false
320340 #stdoutLastWasBlank = false
321341 #logCallCount = 0
322- #constructorArgs: unknown [ ]
323342 #options: Record < string , unknown >
324343 #originalStdout?: any
325344
@@ -345,8 +364,7 @@ export class Logger {
345364 * ```
346365 */
347366 constructor ( ...args : unknown [ ] ) {
348- // Store constructor args for child loggers
349- this . #constructorArgs = args
367+ // Store constructor args for lazy Console initialization.
350368 privateConstructorArgs . set ( this , args )
351369
352370 // Store options if provided (for future extensibility)
@@ -377,8 +395,9 @@ export class Logger {
377395 let con = privateConsole . get ( this )
378396 if ( ! con ) {
379397 // Lazy initialization - create Console on first use.
380- if ( this . #constructorArgs. length ) {
381- con = constructConsole ( ...this . #constructorArgs)
398+ const ctorArgs = privateConstructorArgs . get ( this ) ?? [ ]
399+ if ( ctorArgs . length ) {
400+ con = constructConsole ( ...ctorArgs )
382401 } else {
383402 // Create a new console that acts like the builtin one so that it will
384403 // work with Node's --frozen-intrinsics flag.
@@ -420,8 +439,9 @@ export class Logger {
420439 */
421440 get stderr ( ) : Logger {
422441 if ( ! this . #stderrLogger) {
423- // Pass parent's constructor args to maintain config
424- const instance = new Logger ( ...this . #constructorArgs)
442+ // Pass parent's constructor args to maintain config.
443+ const ctorArgs = privateConstructorArgs . get ( this ) ?? [ ]
444+ const instance = new Logger ( ...ctorArgs )
425445 instance . #parent = this
426446 instance . #boundStream = 'stderr'
427447 instance . #options = { __proto__ : null , ...this . #options }
@@ -453,8 +473,9 @@ export class Logger {
453473 */
454474 get stdout ( ) : Logger {
455475 if ( ! this . #stdoutLogger) {
456- // Pass parent's constructor args to maintain config
457- const instance = new Logger ( ...this . #constructorArgs)
476+ // Pass parent's constructor args to maintain config.
477+ const ctorArgs = privateConstructorArgs . get ( this ) ?? [ ]
478+ const instance = new Logger ( ...ctorArgs )
458479 instance . #parent = this
459480 instance . #boundStream = 'stdout'
460481 instance . #options = { __proto__ : null , ...this . #options }
@@ -1447,9 +1468,10 @@ export class Logger {
14471468 // 1. Use stored reference from constructor options
14481469 // 2. Try to get from constructor args
14491470 // 3. Fall back to con._stdout (which applies formatting)
1471+ const ctorArgs = privateConstructorArgs . get ( this ) ?? [ ]
14501472 const stdout =
14511473 this . #originalStdout ||
1452- ( this . #constructorArgs [ 0 ] as any ) ?. stdout ||
1474+ ( ctorArgs [ 0 ] as any ) ?. stdout ||
14531475 con . _stdout
14541476 stdout . write ( text )
14551477 this [ lastWasBlankSymbol ] ( false )
@@ -1564,9 +1586,11 @@ Object.defineProperties(
15641586 if ( con === undefined ) {
15651587 // Lazy initialization - this will only happen if someone calls a
15661588 // dynamically added console method before any core logger method.
1567- const constructorArgs = privateConstructorArgs . get ( this ) || [ ]
1568- if ( constructorArgs . length ) {
1569- con = constructConsole ( ...constructorArgs )
1589+ const ctorArgs = privateConstructorArgs . get ( this ) ?? [ ]
1590+ // Clean up constructor args - no longer needed after Console creation.
1591+ privateConstructorArgs . delete ( this )
1592+ if ( ctorArgs . length ) {
1593+ con = constructConsole ( ...ctorArgs )
15701594 } else {
15711595 con = constructConsole ( {
15721596 stdout : process . stdout ,
@@ -1577,8 +1601,7 @@ Object.defineProperties(
15771601 }
15781602 }
15791603 privateConsole . set ( this , con )
1580- // Clean up constructor args - no longer needed after Console creation.
1581- privateConstructorArgs . delete ( this )
1604+
15821605 }
15831606 const result = ( con as any ) [ key ] ( ...args )
15841607 return result === undefined || result === con ? this : result
0 commit comments