Skip to content

Commit 92f681b

Browse files
committed
lib: refactor logger to use diagnostics_channel
1 parent e149af2 commit 92f681b

File tree

2 files changed

+190
-153
lines changed

2 files changed

+190
-153
lines changed

lib/logger.js

Lines changed: 67 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ const {
2323
} = require('internal/validators');
2424

2525
const 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
2839
const LEVELS = {
@@ -39,7 +50,11 @@ const LEVEL_NAMES = ObjectKeys(LEVELS);
3950
// Noop function for disabled log levels
4051
function 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-
*/
409370
function createLogger(options) {
410371
return new Logger(options);
411372
}
412373

413374
module.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

Comments
 (0)