@@ -23,6 +23,17 @@ const {
2323} = require ( 'internal/validators' ) ;
2424
2525const Utf8Stream = require ( 'internal/streams/fast-utf8-stream' ) ;
26+ const diagnosticsChannel = require ( 'diagnostics_channel' ) ;
27+
28+ // Create channels for each log level
29+ const channels = {
30+ trace : diagnosticsChannel . channel ( 'log:trace' ) ,
31+ debug : diagnosticsChannel . channel ( 'log:debug' ) ,
32+ info : diagnosticsChannel . channel ( 'log:info' ) ,
33+ warn : diagnosticsChannel . channel ( 'log:warn' ) ,
34+ error : diagnosticsChannel . channel ( 'log:error' ) ,
35+ fatal : diagnosticsChannel . channel ( 'log:fatal' ) ,
36+ } ;
2637
2738// RFC5424 numerical ordering + log4j interface
2839const LEVELS = {
@@ -39,7 +50,11 @@ const LEVEL_NAMES = ObjectKeys(LEVELS);
3950// Noop function for disabled log levels
4051function noop ( ) { }
4152
42- class Handler {
53+ /**
54+ * Base consumer class for handling log records
55+ * Consumers subscribe to diagnostics_channel events
56+ */
57+ class LogConsumer {
4358 constructor ( options = { } ) {
4459 validateObject ( options , 'options' ) ;
4560 const { level = 'info' } = options ;
@@ -51,11 +66,11 @@ class Handler {
5166 }
5267
5368 this . _level = level ;
54- this . _levelValue = LEVELS [ level ] ; // Cache numeric value
69+ this . _levelValue = LEVELS [ level ] ;
5570 }
5671
5772 /**
58- * Check if a level would be logged
73+ * Check if a level would be consumed
5974 * @param {string } level
6075 * @returns {boolean }
6176 */
@@ -64,18 +79,39 @@ class Handler {
6479 }
6580
6681 /**
67- * Handle a log record
82+ * Attach this consumer to log channels
83+ */
84+ attach ( ) {
85+ for ( const level of LEVEL_NAMES ) {
86+ if ( this . enabled ( level ) ) {
87+ channels [ level ] . subscribe ( this . _handleLog . bind ( this , level ) ) ;
88+ }
89+ }
90+ }
91+
92+ /**
93+ * Internal handler called by diagnostics_channel
94+ * @param {string } level
95+ * @param {object } record
96+ * @private
97+ */
98+ _handleLog ( level , record ) {
99+ this . handle ( record ) ;
100+ }
101+
102+ /**
103+ * Handle a log record (must be implemented by subclass)
68104 * @param {object } record
69105 */
70106 handle ( record ) {
71- throw new ERR_METHOD_NOT_IMPLEMENTED ( 'Handler .handle()' ) ;
107+ throw new ERR_METHOD_NOT_IMPLEMENTED ( 'LogConsumer .handle()' ) ;
72108 }
73109}
74110
75111/**
76- * JSON handler - outputs structured JSON logs
112+ * JSON consumer - outputs structured JSON logs
77113 */
78- class JSONHandler extends Handler {
114+ class JSONConsumer extends LogConsumer {
79115 constructor ( options = { } ) {
80116 super ( options ) ;
81117 const {
@@ -85,24 +121,20 @@ class JSONHandler extends Handler {
85121
86122 validateObject ( fields , 'options.fields' ) ;
87123
88- // Default to stdout
89124 this . _stream = stream ? this . _createStream ( stream ) :
90125 new Utf8Stream ( { fd : 1 } ) ;
91126 this . _fields = fields ;
92127 }
93128
94129 _createStream ( stream ) {
95- // If it's already a Utf8Stream, use it directly
96130 if ( stream instanceof Utf8Stream ) {
97131 return stream ;
98132 }
99133
100- // If it's a file descriptor number
101134 if ( typeof stream === 'number' ) {
102135 return new Utf8Stream ( { fd : stream } ) ;
103136 }
104137
105- // If it's a path string
106138 if ( typeof stream === 'string' ) {
107139 return new Utf8Stream ( { dest : stream } ) ;
108140 }
@@ -113,17 +145,13 @@ class JSONHandler extends Handler {
113145 }
114146
115147 handle ( record ) {
116- // Note: Level check already done in Logger._log()
117- // No need to check again here
118-
119- // Build the JSON log record
120148 const logObj = {
121149 level : record . level ,
122150 time : record . time ,
123151 msg : record . msg ,
124- ...this . _fields , // Additional fields (hostname, pid, etc)
125- ...record . bindings , // Parent context
126- ...record . fields , // Log-specific fields
152+ ...this . _fields ,
153+ ...record . bindings ,
154+ ...record . fields ,
127155 } ;
128156
129157 const json = JSONStringify ( logObj ) + '\n' ;
@@ -146,7 +174,7 @@ class JSONHandler extends Handler {
146174 }
147175
148176 /**
149- * Close the handler
177+ * Close the consumer
150178 */
151179 end ( ) {
152180 this . _stream . end ( ) ;
@@ -160,34 +188,22 @@ class Logger {
160188 constructor ( options = { } ) {
161189 validateObject ( options , 'options' ) ;
162190 const {
163- handler = new JSONHandler ( ) ,
164- level,
191+ level = 'info' ,
165192 bindings = { } ,
166193 } = options ;
167194
168- if ( ! ( handler instanceof Handler ) ) {
169- throw new ERR_INVALID_ARG_TYPE ( 'options.handler' , 'Handler' , handler ) ;
195+ validateString ( level , 'options.level' ) ;
196+ if ( ! LEVELS [ level ] ) {
197+ throw new ERR_INVALID_ARG_VALUE ( 'options.level' , level ,
198+ `must be one of: ${ LEVEL_NAMES . join ( ', ' ) } ` ) ;
170199 }
171200
172201 validateObject ( bindings , 'options.bindings' ) ;
173202
174- this . _handler = handler ;
203+ this . _level = level ;
204+ this . _levelValue = LEVELS [ level ] ;
175205 this . _bindings = bindings ;
176206
177- // If level is specified, it overrides handler's level
178- if ( level !== undefined ) {
179- validateString ( level , 'options.level' ) ;
180- if ( ! LEVELS [ level ] ) {
181- throw new ERR_INVALID_ARG_VALUE ( 'options.level' , level ,
182- `must be one of: ${ LEVEL_NAMES . join ( ', ' ) } ` ) ;
183- }
184- this . _level = level ;
185- this . _levelValue = LEVELS [ level ] ; // Cache numeric value
186- } else {
187- this . _level = handler . _level ;
188- this . _levelValue = handler . _levelValue ; // Use handler's cached value
189- }
190-
191207 // Optimize: replace disabled log methods with noop
192208 this . _setLogMethods ( ) ;
193209 }
@@ -199,8 +215,6 @@ class Logger {
199215 _setLogMethods ( ) {
200216 const levelValue = this . _levelValue ;
201217
202- // Override instance methods for disabled levels
203- // This avoids the level check on every call
204218 if ( levelValue > 10 ) this . trace = noop ;
205219 if ( levelValue > 20 ) this . debug = noop ;
206220 if ( levelValue > 30 ) this . info = noop ;
@@ -228,16 +242,13 @@ class Logger {
228242 validateObject ( bindings , 'bindings' ) ;
229243 validateObject ( options , 'options' ) ;
230244
231- // Shallow merge parent and child bindings
232245 const mergedBindings = ObjectAssign (
233246 { __proto__ : null } ,
234247 this . _bindings ,
235248 bindings ,
236249 ) ;
237250
238- // Create new logger inheriting handler
239251 return new Logger ( {
240- handler : this . _handler ,
241252 level : options . level || this . _level ,
242253 bindings : mergedBindings ,
243254 } ) ;
@@ -264,28 +275,24 @@ class Logger {
264275 * @param {object } [fields] - Optional fields to merge
265276 */
266277 _log ( level , levelValue , msgOrObj , fields ) {
278+ if ( levelValue < this . _levelValue ) {
279+ return ;
280+ }
281+
267282 let msg ;
268283 let logFields ;
269284
270- // Support two signatures:
271- // 1. logger.info('message', { fields })
272- // 2. logger.info({ msg: 'message', other: 'fields' })
273- // 3. logger.info(new Error('boom'))
274-
275285 if ( this . _isError ( msgOrObj ) ) {
276- // Handle Error as first argument
277286 msg = msgOrObj . message ;
278287 logFields = {
279288 err : this . _serializeError ( msgOrObj ) ,
280289 ...fields ,
281290 } ;
282291 } else if ( typeof msgOrObj === 'string' ) {
283- // Support String message
284292 msg = msgOrObj ;
285293 logFields = fields || { } ;
286294 validateObject ( logFields , 'fields' ) ;
287295 } else {
288- // Support object with msg property
289296 validateObject ( msgOrObj , 'obj' ) ;
290297 if ( typeof msgOrObj . msg !== 'string' ) {
291298 throw new ERR_INVALID_ARG_TYPE ( 'obj.msg' , 'string' , msgOrObj . msg ) ;
@@ -294,13 +301,11 @@ class Logger {
294301 msg = extractedMsg ;
295302 logFields = restFields ;
296303
297- // Serialize Error objects in fields
298304 if ( logFields . err && this . _isError ( logFields . err ) ) {
299305 logFields . err = this . _serializeError ( logFields . err ) ;
300306 }
301307 }
302308
303- // Build log record
304309 const record = {
305310 level,
306311 msg,
@@ -309,7 +314,10 @@ class Logger {
309314 fields : logFields ,
310315 } ;
311316
312- this . _handler . handle ( record ) ;
317+ const channel = channels [ level ] ;
318+ if ( channel . hasSubscribers ) {
319+ channel . publish ( record ) ;
320+ }
313321 }
314322
315323 /**
@@ -325,95 +333,51 @@ class Logger {
325333 stack : err . stack ,
326334 } ;
327335
328- // Add code if it exists
329336 serialized . code ||= err . code ;
330337
331- // Add any enumerable custom properties
332338 for ( const key in err ) {
333339 serialized [ key ] ||= err [ key ] ;
334340 }
335341
336342 return serialized ;
337343 }
338344
339- /**
340- * Log at trace level
341- * @param {string|object } msgOrObj - Message string or object with msg property
342- * @param {object } [fields] - Optional fields to merge
343- */
344345 trace ( msgOrObj , fields ) {
345346 this . _log ( 'trace' , 10 , msgOrObj , fields ) ;
346347 }
347348
348- /**
349- * Log at debug level
350- * @param {string|object } msgOrObj - Message string or object with msg property
351- * @param {object } [fields] - Optional fields to merge
352- */
353349 debug ( msgOrObj , fields ) {
354350 this . _log ( 'debug' , 20 , msgOrObj , fields ) ;
355351 }
356352
357- /**
358- * Log at info level
359- * @param {string|object } msgOrObj - Message string or object with msg property
360- * @param {object } [fields] - Optional fields to merge
361- */
362353 info ( msgOrObj , fields ) {
363354 this . _log ( 'info' , 30 , msgOrObj , fields ) ;
364355 }
365356
366- /**
367- * Log at warn level
368- * @param {string|object } msgOrObj - Message string or object with msg property
369- * @param {object } [fields] - Optional fields to merge
370- */
371357 warn ( msgOrObj , fields ) {
372358 this . _log ( 'warn' , 40 , msgOrObj , fields ) ;
373359 }
374360
375- /**
376- * Log at error level
377- * @param {string|object } msgOrObj - Message string or object with msg property
378- * @param {object } [fields] - Optional fields to merge
379- */
380361 error ( msgOrObj , fields ) {
381362 this . _log ( 'error' , 50 , msgOrObj , fields ) ;
382363 }
383364
384- /**
385- * Log at fatal level (does NOT exit the process)
386- * @param {string|object } msgOrObj - Message string or object with msg property
387- * @param {object } [fields] - Optional fields to merge
388- */
389365 fatal ( msgOrObj , fields ) {
390366 this . _log ( 'fatal' , 60 , msgOrObj , fields ) ;
391367 }
392-
393- /**
394- * Flush pending writes
395- * @param {Function } callback
396- */
397- flush ( callback ) {
398- this . _handler . flush ( callback ) ;
399- }
400368}
401369
402- /**
403- * Create a new logger instance
404- * @param {object } [options]
405- * @param {Handler } [options.handler] - Output handler (default: JSONHandler)
406- * @param {string } [options.level] - Minimum log level (default: 'info')
407- * @returns {Logger }
408- */
409370function createLogger ( options ) {
410371 return new Logger ( options ) ;
411372}
412373
413374module . exports = {
414375 createLogger,
415376 Logger,
416- Handler,
417- JSONHandler,
377+ LogConsumer,
378+ JSONConsumer,
379+ Handler : LogConsumer ,
380+ JSONHandler : JSONConsumer ,
418381 LEVELS ,
382+ channels,
419383} ;
0 commit comments