Skip to content

Commit e87d4ec

Browse files
committed
add serializers support to logger API
1 parent 981d1cd commit e87d4ec

File tree

3 files changed

+366
-12
lines changed

3 files changed

+366
-12
lines changed

lib/internal/logger/serializers.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
/**
4+
* Serializes an Error object
5+
* @param {Error} error
6+
* @returns {object}
7+
*/
8+
function serializeErr(error) {
9+
if (!error || typeof error !== 'object') {
10+
return error;
11+
}
12+
13+
const obj = {
14+
type: error.constructor.name,
15+
message: error.message,
16+
stack: error.stack,
17+
};
18+
19+
// Include additional error properties
20+
for (const key in error) {
21+
if (obj[key] === undefined) {
22+
obj[key] = error[key];
23+
}
24+
}
25+
26+
// Handle error code if present
27+
if (error.code !== undefined) {
28+
obj.code = error.code;
29+
}
30+
31+
// Handle error cause recursively
32+
if (error.cause !== undefined) {
33+
obj.cause = typeof error.cause === 'object' && error.cause !== null ?
34+
serializeErr(error.cause) :
35+
error.cause;
36+
}
37+
38+
return obj;
39+
}
40+
41+
/**
42+
* Serializes HTTP request object
43+
* @param {object} req - HTTP request
44+
* @returns {object}
45+
*/
46+
function req(req) {
47+
return {
48+
method: req.method,
49+
url: req.url,
50+
headers: req.headers,
51+
remoteAddress: req.socket?.remoteAddress,
52+
remotePort: req.socket?.remotePort,
53+
};
54+
}
55+
56+
/**
57+
* Serializes HTTP response object
58+
* @param {object} res - HTTP response
59+
* @returns {object}
60+
*/
61+
function res(res) {
62+
return {
63+
statusCode: res.statusCode,
64+
headers: res.getHeaders ? res.getHeaders() : res.headers,
65+
};
66+
}
67+
68+
module.exports = {
69+
err: serializeErr,
70+
req,
71+
res,
72+
};

lib/logger.js

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const {
44
DateNow,
5-
Error,
5+
ErrorIsError,
66
JSONStringify,
77
ObjectAssign,
88
ObjectKeys,
@@ -21,11 +21,13 @@ const {
2121
validateObject,
2222
validateString,
2323
validateOneOf,
24+
validateFunction,
2425
} = require('internal/validators');
2526

2627
const Utf8Stream = require('internal/streams/fast-utf8-stream');
2728
const diagnosticsChannel = require('diagnostics_channel');
2829
const { kEmptyObject } = require('internal/util');
30+
const stdSerializers = require('internal/logger/serializers');
2931

3032
// Create channels for each log level
3133
const channels = {
@@ -69,7 +71,6 @@ class LogConsumer {
6971

7072
validateOneOf(level, 'options.level', LEVEL_NAMES);
7173

72-
this.#level = level;
7374
this.#levelValue = LEVELS[level];
7475
}
7576

@@ -220,10 +221,15 @@ class Logger {
220221
#level;
221222
#levelValue;
222223
#bindings;
224+
#serializers;
223225

224226
constructor(options = kEmptyObject) {
225227
validateObject(options, 'options');
226-
const { level = 'info', bindings = kEmptyObject } = options;
228+
const {
229+
level = 'info',
230+
bindings = kEmptyObject,
231+
serializers = kEmptyObject,
232+
} = options;
227233

228234
validateString(level, 'options.level');
229235
if (!LEVELS[level]) {
@@ -235,11 +241,26 @@ class Logger {
235241
}
236242

237243
validateObject(bindings, 'options.bindings');
244+
validateObject(serializers, 'options.serializers');
245+
246+
// Validate serializers are functions
247+
for (const key in serializers) {
248+
validateFunction(serializers[key], `options.serializers.${key}`);
249+
}
238250

239251
this.#level = level;
240252
this.#levelValue = LEVELS[level];
241253
this.#bindings = bindings;
242254

255+
// Create serializers object with default err serializer
256+
this.#serializers = { __proto__: null };
257+
// Add default err serializer (can be overridden)
258+
this.#serializers.err = stdSerializers.err;
259+
// Add custom serializers
260+
for (const key in serializers) {
261+
this.#serializers[key] = serializers[key];
262+
}
263+
243264
this.#setLogMethods();
244265
}
245266

@@ -283,9 +304,30 @@ class Logger {
283304
bindings,
284305
);
285306

307+
// Handle serializers inheritance
308+
let childSerializers;
309+
if (options.serializers) {
310+
validateObject(options.serializers, 'options.serializers');
311+
312+
// Create new serializers object
313+
childSerializers = { __proto__: null };
314+
315+
// Copy parent serializers
316+
for (const key in this.#serializers) {
317+
childSerializers[key] = this.#serializers[key];
318+
}
319+
320+
// Override with child serializers
321+
for (const key in options.serializers) {
322+
validateFunction(options.serializers[key], `options.serializers.${key}`);
323+
childSerializers[key] = options.serializers[key];
324+
}
325+
}
326+
286327
const childLogger = new Logger({
287328
level: options.level || this.#level,
288329
bindings: mergedBindings,
330+
serializers: childSerializers || this.#serializers,
289331
});
290332

291333
return childLogger;
@@ -298,7 +340,32 @@ class Logger {
298340
* @private
299341
*/
300342
#isError(value) {
301-
return Error.isError(value);
343+
return ErrorIsError(value);
344+
}
345+
346+
/**
347+
* Apply serializers to an object's properties
348+
* @param {object} obj - Object to serialize
349+
* @returns {object} Serialized object
350+
* @private
351+
*/
352+
#applySerializers(obj) {
353+
if (!obj || typeof obj !== 'object') {
354+
return obj;
355+
}
356+
357+
const serialized = { __proto__: null };
358+
const serializers = this.#serializers;
359+
360+
for (const key in obj) {
361+
const value = obj[key];
362+
// Apply serializer if exists for this key
363+
serialized[key] = serializers[key] ?
364+
serializers[key](value) :
365+
value;
366+
}
367+
368+
return serialized;
302369
}
303370

304371
/**
@@ -332,24 +399,44 @@ class Logger {
332399

333400
if (this.#isError(msgOrObj)) {
334401
msg = msgOrObj.message;
402+
// Use err serializer for Error objects
403+
const serializedErr = this.#serializers.err ?
404+
this.#serializers.err(msgOrObj) :
405+
this.#serializeError(msgOrObj);
406+
335407
logFields = {
336-
err: this.#serializeError(msgOrObj),
337-
...fields,
408+
err: serializedErr,
338409
};
410+
411+
// Apply serializers to additional fields
412+
if (fields) {
413+
const serializedFields = this.#applySerializers(fields);
414+
for (const key in serializedFields) {
415+
logFields[key] = serializedFields[key];
416+
}
417+
}
339418
} else if (typeof msgOrObj === 'string') {
340419
msg = msgOrObj;
341-
logFields = fields ?? kEmptyObject;
420+
// Apply serializers to fields
421+
logFields = fields ? this.#applySerializers(fields) : kEmptyObject;
342422
} else {
343423
const { msg: extractedMsg, ...restFields } = msgOrObj;
344424
msg = extractedMsg;
345-
logFields = restFields;
346425

347-
if (logFields.err && this.#isError(logFields.err)) {
348-
logFields.err = this.#serializeError(logFields.err);
426+
// Apply serializers to object fields
427+
logFields = this.#applySerializers(restFields);
428+
429+
// Special handling for err/error fields
430+
if (logFields.err && this.#isError(restFields.err)) {
431+
logFields.err = this.#serializers.err ?
432+
this.#serializers.err(restFields.err) :
433+
this.#serializeError(restFields.err);
349434
}
350435

351-
if (logFields.error && this.#isError(logFields.error)) {
352-
logFields.error = this.#serializeError(logFields.error);
436+
if (logFields.error && this.#isError(restFields.error)) {
437+
logFields.error = this.#serializers.err ?
438+
this.#serializers.err(restFields.error) :
439+
this.#serializeError(restFields.error);
353440
}
354441
}
355442

@@ -446,6 +533,7 @@ module.exports = {
446533
LEVELS,
447534
channels,
448535
createLogger,
536+
stdSerializers,
449537
Handler: LogConsumer,
450538
JSONHandler: JSONConsumer,
451539
};

0 commit comments

Comments
 (0)