55 ObjectAssign,
66 ObjectDefineProperty,
77 ObjectKeys,
8- SafeSet,
98 SymbolFor,
109} = primordials ;
1110
@@ -76,12 +75,14 @@ function noop() {}
7675class LogConsumer {
7776 /** @type {number } */
7877 #levelValue;
78+ #handlers;
7979
8080 constructor ( options = kEmptyObject ) {
8181 validateObject ( options , 'options' ) ;
8282 const { level = 'info' } = options ;
8383 validateOneOf ( level , 'options.level' , LEVEL_NAMES ) ;
8484 this . #levelValue = LEVELS [ level ] ;
85+ this . #handlers = { __proto__ : null } ;
8586
8687 // Setup level-specific enabled properties for typo safety
8788 // Allows consumer.info.enabled instead of consumer.enabled('info')
@@ -98,7 +99,21 @@ class LogConsumer {
9899 attach ( ) {
99100 for ( const level of LEVEL_NAMES ) {
100101 if ( this [ level ] . enabled ) {
101- channels [ level ] . subscribe ( this . handle . bind ( this ) ) ;
102+ const handler = this . handle . bind ( this ) ;
103+ this . #handlers[ level ] = handler ;
104+ channels [ level ] . subscribe ( handler ) ;
105+ }
106+ }
107+ }
108+
109+ /**
110+ * Detach this consumer from log channels
111+ */
112+ detach ( ) {
113+ for ( const level of LEVEL_NAMES ) {
114+ if ( this . #handlers[ level ] ) {
115+ channels [ level ] . unsubscribe ( this . #handlers[ level ] ) ;
116+ this . #handlers[ level ] = undefined ;
102117 }
103118 }
104119 }
@@ -164,15 +179,13 @@ class JSONConsumer extends LogConsumer {
164179
165180 handle ( record ) {
166181 // Start building JSON manually
167- let json = `{"level":"${ record . level } ","time":${ record . time } ,"msg":"${ record . msg } "` ;
182+ // record.level is trusted internal value, record.msg is user input and must be escaped
183+ let json = `{"level":"${ record . level } ","time":${ record . time } ,"msg":${ JSONStringify ( record . msg ) } ` ;
168184
169185 // Add consumer fields
170186 const consumerFields = this . #fields;
171187 for ( const key of ObjectKeys ( consumerFields ) ) {
172- const value = consumerFields [ key ] ;
173- json += typeof value === 'string' ?
174- `,"${ key } ":"${ value } "` :
175- `,"${ key } ":${ JSONStringify ( value ) } ` ;
188+ json += `,${ JSONStringify ( key ) } :${ JSONStringify ( consumerFields [ key ] ) } ` ;
176189 }
177190
178191 // Add pre-serialized bindings
@@ -181,10 +194,7 @@ class JSONConsumer extends LogConsumer {
181194 // Add log fields
182195 const fields = record . fields ;
183196 for ( const key in fields ) {
184- const value = fields [ key ] ;
185- json += typeof value === 'string' ?
186- `,"${ key } ":"${ value } "` :
187- `,"${ key } ":${ JSONStringify ( value ) } ` ;
197+ json += `,${ JSONStringify ( key ) } :${ JSONStringify ( fields [ key ] ) } ` ;
188198 }
189199
190200 json += '}\n' ;
@@ -285,7 +295,7 @@ class Logger {
285295 const keys = ObjectKeys ( bindings ) ;
286296 for ( const key of keys ) {
287297 const serialized = this . #serializeValue( bindings [ key ] , key ) ;
288- result += `," ${ key } " :${ JSONStringify ( serialized ) } ` ;
298+ result += `,${ JSONStringify ( key ) } :${ JSONStringify ( serialized ) } ` ;
289299 }
290300 return result ;
291301 }
@@ -480,13 +490,8 @@ class Logger {
480490
481491 if ( isNativeError ( msgOrObj ) ) {
482492 msg = msgOrObj . message ;
483- // Use err serializer for Error objects
484- const serializedErr = this . #serializers. err ?
485- this . #serializers. err ( msgOrObj ) :
486- this . #serializeError( msgOrObj ) ;
487-
488493 logFields = {
489- err : serializedErr ,
494+ err : this . #serializers . err ( msgOrObj ) ,
490495 } ;
491496
492497 // Apply serializers to additional fields
@@ -508,17 +513,13 @@ class Logger {
508513 // Apply serializers to object fields
509514 logFields = this . #applySerializers( restFields ) ;
510515
511- // Special handling for err/error fields
516+ // Re-serialize err/error fields with err serializer for proper Error handling
512517 if ( logFields . err && isNativeError ( restFields . err ) ) {
513- logFields . err = this . #serializers. err ?
514- this . #serializers. err ( restFields . err ) :
515- this . #serializeError( restFields . err ) ;
518+ logFields . err = this . #serializers. err ( restFields . err ) ;
516519 }
517520
518521 if ( logFields . error && isNativeError ( restFields . error ) ) {
519- logFields . error = this . #serializers. err ?
520- this . #serializers. err ( restFields . error ) :
521- this . #serializeError( restFields . error ) ;
522+ logFields . error = this . #serializers. err ( restFields . error ) ;
522523 }
523524 }
524525
@@ -533,46 +534,6 @@ class Logger {
533534 channel . publish ( record ) ;
534535 }
535536
536- /**
537- * Serialize Error object for logging (with recursive cause handling)
538- * @param {object } err - Error object to serialize
539- * @param {Set } [seen] - Set to track circular references
540- * @returns {object|string } Serialized error object or '[Circular]'
541- * @private
542- */
543- #serializeError( err , seen = new SafeSet ( ) ) {
544- if ( seen . has ( err ) ) {
545- return '[Circular]' ;
546- }
547- seen . add ( err ) ;
548-
549- const serialized = {
550- message : err . message ,
551- name : err . name ,
552- stack : err . stack ,
553- } ;
554-
555- if ( err . code !== undefined ) {
556- serialized . code = err . code ;
557- }
558-
559- if ( err . cause !== undefined ) {
560- serialized . cause = isNativeError ( err . cause ) ?
561- this . #serializeError( err . cause , seen ) :
562- err . cause ;
563- }
564-
565- // Include additional own error properties (avoid prototype chain)
566- const keys = ObjectKeys ( err ) ;
567- for ( let i = 0 ; i < keys . length ; i ++ ) {
568- const key = keys [ i ] ;
569- if ( serialized [ key ] === undefined ) {
570- serialized [ key ] = err [ key ] ;
571- }
572- }
573-
574- return serialized ;
575- }
576537}
577538
578539module . exports = {
0 commit comments