55 ObjectAssign,
66 ObjectDefineProperty,
77 ObjectKeys,
8+ SafeWeakMap,
89 SymbolFor,
910} = primordials ;
1011
@@ -38,17 +39,6 @@ const { emitExperimentalWarning, kEmptyObject } = require('internal/util');
3839emitExperimentalWarning ( 'Logger' ) ;
3940const stdSerializers = require ( 'internal/logger/serializers' ) ;
4041
41- // Create channels for each log level
42- const channels = {
43- __proto__ : null ,
44- trace : diagnosticsChannel . channel ( 'log:trace' ) ,
45- debug : diagnosticsChannel . channel ( 'log:debug' ) ,
46- info : diagnosticsChannel . channel ( 'log:info' ) ,
47- warn : diagnosticsChannel . channel ( 'log:warn' ) ,
48- error : diagnosticsChannel . channel ( 'log:error' ) ,
49- fatal : diagnosticsChannel . channel ( 'log:fatal' ) ,
50- } ;
51-
5242/*
5343* RFC5424 numerical ordering + log4j interface
5444* @link https://www.rfc-editor.org/rfc/rfc5424.html
@@ -63,6 +53,8 @@ const LEVELS = {
6353 fatal : 60 ,
6454} ;
6555
56+ const channel = diagnosticsChannel . channel ( 'log' ) ;
57+
6658const LEVEL_NAMES = ObjectKeys ( LEVELS ) ;
6759
6860function isReservedKey ( key ) {
@@ -79,15 +71,15 @@ function noop() {}
7971class LogConsumer {
8072 /** @type {number } */
8173 #levelValue;
82- #handlers ;
74+ #handler ;
8375 #attached;
8476
8577 constructor ( options = kEmptyObject ) {
8678 validateObject ( options , 'options' ) ;
8779 const { level = 'info' } = options ;
8880 validateOneOf ( level , 'options.level' , LEVEL_NAMES ) ;
8981 this . #levelValue = LEVELS [ level ] ;
90- this . #handlers = { __proto__ : null } ;
82+ this . #handler = undefined ;
9183 this . #attached = false ;
9284
9385 // Setup level-specific enabled properties for typo safety
@@ -100,20 +92,20 @@ class LogConsumer {
10092 }
10193
10294 /**
103- * Attach this consumer to log channels
95+ * Attach this consumer to log channel
10496 */
10597 attach ( ) {
10698 if ( this . #attached) {
10799 return ;
108100 }
109101 this . #attached = true ;
110- for ( const level of LEVEL_NAMES ) {
111- if ( this [ level ] . enabled ) {
112- const handler = this . handle . bind ( this ) ;
113- this . #handlers[ level ] = handler ;
114- channels [ level ] . subscribe ( handler ) ;
102+ this . #handler = ( record ) => {
103+ if ( record . levelValue < this . #levelValue) {
104+ return ;
115105 }
116- }
106+ this . handle ( record ) ;
107+ } ;
108+ channel . subscribe ( this . #handler) ;
117109 }
118110
119111 /**
@@ -124,12 +116,8 @@ class LogConsumer {
124116 return ;
125117 }
126118 this . #attached = false ;
127- for ( const level of LEVEL_NAMES ) {
128- if ( this . #handlers[ level ] ) {
129- channels [ level ] . unsubscribe ( this . #handlers[ level ] ) ;
130- this . #handlers[ level ] = undefined ;
131- }
132- }
119+ channel . unsubscribe ( this . #handler) ;
120+ this . #handler = undefined ;
133121 }
134122
135123 /**
@@ -147,6 +135,7 @@ class LogConsumer {
147135class JSONConsumer extends LogConsumer {
148136 #stream;
149137 #fields;
138+ #bindingsCache;
150139
151140 constructor ( options = kEmptyObject ) {
152141 super ( options ) ;
@@ -160,6 +149,7 @@ class JSONConsumer extends LogConsumer {
160149 this . #stream = stream ? this . #createStream( stream ) :
161150 new Utf8Stream ( { fd : 1 } ) ;
162151 this . #fields = fields ;
152+ this . #bindingsCache = new SafeWeakMap ( ) ;
163153 }
164154
165155 #createStream( stream ) {
@@ -199,6 +189,10 @@ class JSONConsumer extends LogConsumer {
199189 // record.level is trusted internal value, record.msg is user input and must be escaped
200190 let json = `{"level":"${ record . level } ","time":${ record . time } ,"msg":${ JSONStringify ( record . msg ) } ` ;
201191
192+ if ( record . name !== undefined ) {
193+ json += `,${ JSONStringify ( 'name' ) } :${ JSONStringify ( record . name ) } ` ;
194+ }
195+
202196 const consumerFields = this . #fields;
203197 const consumerKeys = ObjectKeys ( consumerFields ) ;
204198 for ( let i = 0 ; i < consumerKeys . length ; i ++ ) {
@@ -209,8 +203,8 @@ class JSONConsumer extends LogConsumer {
209203 json += `,${ JSONStringify ( key ) } :${ JSONStringify ( value ) } ` ;
210204 }
211205
212- // Add pre-serialized bindings
213- json += record . bindingsStr ;
206+ // Add bindings, cached per bindings object to avoid rebuilding on every log
207+ json += this . #serializeBindings ( record . bindings ) ;
214208
215209 // Add log fields
216210 const fields = record . fields ;
@@ -227,6 +221,30 @@ class JSONConsumer extends LogConsumer {
227221 this . #stream. write ( json ) ;
228222 }
229223
224+ #serializeBindings( bindings ) {
225+ if ( ! bindings || typeof bindings !== 'object' ) {
226+ return '' ;
227+ }
228+
229+ const cached = this . #bindingsCache. get ( bindings ) ;
230+ if ( cached !== undefined ) {
231+ return cached ;
232+ }
233+
234+ let result = '' ;
235+ const keys = ObjectKeys ( bindings ) ;
236+ for ( let i = 0 ; i < keys . length ; i ++ ) {
237+ const key = keys [ i ] ;
238+ if ( isReservedKey ( key ) ) continue ;
239+ const value = bindings [ key ] ;
240+ if ( value === undefined ) continue ;
241+ result += `,${ JSONStringify ( key ) } :${ JSONStringify ( value ) } ` ;
242+ }
243+
244+ this . #bindingsCache. set ( bindings , result ) ;
245+ return result ;
246+ }
247+
230248 /**
231249 * Flush pending writes
232250 * @param {Function } callback
@@ -259,26 +277,32 @@ class Logger {
259277 #level;
260278 #levelValue;
261279 #bindings;
262- #bindingsStr ; // Pre-serialized bindings JSON string
280+ #name ;
263281 #serializers;
264282
265283 /**
266284 * Create a new Logger instance
267285 * @param {object } [options]
268286 * @param {string } [options.level] - Minimum log level (default: 'info')
287+ * @param {string } [options.name] - Optional logger name/namespace
269288 * @param {object } [options.bindings] - Context fields (default: {})
270289 * @param {object } [options.serializers] - Custom serializers (default: {})
271290 */
272291 constructor ( options = kEmptyObject ) {
273292 validateObject ( options , 'options' ) ;
274293 const {
275294 level = 'info' ,
295+ name,
276296 bindings = kEmptyObject ,
277297 serializers = kEmptyObject ,
278298 } = options ;
279299
280300 validateOneOf ( level , 'options.level' , LEVEL_NAMES ) ;
281301
302+ if ( name !== undefined ) {
303+ validateString ( name , 'options.name' ) ;
304+ }
305+
282306 validateObject ( bindings , 'options.bindings' ) ;
283307 validateObject ( serializers , 'options.serializers' ) ;
284308
@@ -290,6 +314,7 @@ class Logger {
290314
291315 this . #level = level ;
292316 this . #levelValue = LEVELS [ level ] ;
317+ this . #name = name ;
293318 this . #bindings = bindings ;
294319
295320 // Create serializers object with default err serializer
@@ -302,35 +327,10 @@ class Logger {
302327 this . #serializers[ key ] = serializers [ key ] ;
303328 }
304329
305- // Pre-serialize bindings
306- this . #bindingsStr = this . #serializeBindings( bindings ) ;
307330
308331 this . #setLogMethods( ) ;
309332 }
310333
311- /**
312- * Pre-serialize bindings
313- * @param {object } bindings - Context fields to serialize
314- * @returns {string } Pre-serialized bindings as JSON string
315- * @private
316- */
317- #serializeBindings( bindings ) {
318- if ( ! bindings || typeof bindings !== 'object' ) {
319- return '' ;
320- }
321-
322- let result = '' ;
323- const keys = ObjectKeys ( bindings ) ;
324- for ( let i = 0 ; i < keys . length ; i ++ ) {
325- const key = keys [ i ] ;
326- if ( isReservedKey ( key ) ) continue ;
327- const serialized = this . #serializeValue( bindings [ key ] , key ) ;
328- if ( serialized === undefined ) continue ;
329- result += `,${ JSONStringify ( key ) } :${ JSONStringify ( serialized ) } ` ;
330- }
331- return result ;
332- }
333-
334334 /**
335335 * Setup log methods with enabled getters.
336336 *
@@ -502,7 +502,6 @@ class Logger {
502502 return ;
503503 }
504504
505- const channel = channels [ level ] ;
506505 if ( ! channel . hasSubscribers ) {
507506 return ;
508507 }
@@ -557,9 +556,11 @@ class Logger {
557556 const record = {
558557 __proto__ : null ,
559558 level,
559+ levelValue,
560+ name : this . #name,
560561 msg,
561562 time : DateNow ( ) ,
562- bindingsStr : this . #bindingsStr , // Pre-serialized bindings string
563+ bindings : this . #bindings,
563564 fields : logFields ,
564565 } ;
565566
0 commit comments