From 8a33834b69216ecb9b793724f648a6206e1b0c07 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Fri, 27 Sep 2024 01:33:13 +0300 Subject: [PATCH 1/3] Add asObjectBindingsOnly, allowing write to receive only the bindings as object This allows to pass the format string and args as is the underlying console when to keep their special handling, for when using `write` to only modify the default way the printing to the console looks rather than writing to an alternate destination entirely. Part of #1556 --- browser.js | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/browser.js b/browser.js index 522de1a05..2c48bdd88 100644 --- a/browser.js +++ b/browser.js @@ -109,6 +109,7 @@ function pino (opts) { transmit, serialize, asObject: opts.browser.asObject, + asObjectBindingsOnly: opts.browser.asObjectBindingsOnly, formatters: opts.browser.formatters, levels, timestamp: getTimeFunction(opts), @@ -317,7 +318,7 @@ function createWrap (self, opts, rootLogger, level) { argsIsSerialized = true } if (opts.asObject || opts.formatters) { - write.call(proto, asObject(this, level, args, ts, opts)) + write.call(proto, ...asObject(this, level, args, ts, opts)) } else write.apply(proto, args) if (opts.transmit) { @@ -347,30 +348,43 @@ function asObject (logger, level, args, ts, opts) { const argsCloned = args.slice() let msg = argsCloned[0] const logObject = {} - if (ts) { - logObject.time = ts - } - - if (levelFormatter) { - const formattedLevel = levelFormatter(level, logger.levels.values[level]) - Object.assign(logObject, formattedLevel) - } else { - logObject.level = logger.levels.values[level] - } let lvl = (logger._childLevel | 0) + 1 if (lvl < 1) lvl = 1 - // deliberate, catching objects, arrays - if (msg !== null && typeof msg === 'object') { - while (lvl-- && typeof argsCloned[0] === 'object') { - Object.assign(logObject, argsCloned.shift()) + + if (opts.asObjectBindingsOnly) { + if (msg !== null && typeof msg === 'object') { + while (lvl-- && typeof argsCloned[0] === 'object') { + Object.assign(logObject, argsCloned.shift()) + } + } + + const formattedLogObject = logObjectFormatter(logObject) + return [formattedLogObject, ...argsCloned] + } else { + if (ts) { + logObject.time = ts + } + + if (levelFormatter) { + const formattedLevel = levelFormatter(level, logger.levels.values[level]) + Object.assign(logObject, formattedLevel) + } else { + logObject.level = logger.levels.values[level] } - msg = argsCloned.length ? format(argsCloned.shift(), argsCloned) : undefined - } else if (typeof msg === 'string') msg = format(argsCloned.shift(), argsCloned) - if (msg !== undefined) logObject[opts.messageKey] = msg - const formattedLogObject = logObjectFormatter(logObject) - return formattedLogObject + // deliberate, catching objects, arrays + if (msg !== null && typeof msg === 'object') { + while (lvl-- && typeof argsCloned[0] === 'object') { + Object.assign(logObject, argsCloned.shift()) + } + msg = argsCloned.length ? format(argsCloned.shift(), argsCloned) : undefined + } else if (typeof msg === 'string') msg = format(argsCloned.shift(), argsCloned) + if (msg !== undefined) logObject[opts.messageKey] = msg + + const formattedLogObject = logObjectFormatter(logObject) + return [formattedLogObject] + } } function applySerializers (args, serialize, serializers, stdErrSerialize) { From 417b071eff03ea7b7df40f131caec6a14fec08aa Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Thu, 30 Jan 2025 10:41:07 +0200 Subject: [PATCH 2/3] Add a test --- browser.js | 22 +++++++++++----------- test/browser.test.js | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/browser.js b/browser.js index 2c48bdd88..d50bac4a7 100644 --- a/browser.js +++ b/browser.js @@ -352,6 +352,17 @@ function asObject (logger, level, args, ts, opts) { let lvl = (logger._childLevel | 0) + 1 if (lvl < 1) lvl = 1 + if (ts) { + logObject.time = ts + } + + if (levelFormatter) { + const formattedLevel = levelFormatter(level, logger.levels.values[level]) + Object.assign(logObject, formattedLevel) + } else { + logObject.level = logger.levels.values[level] + } + if (opts.asObjectBindingsOnly) { if (msg !== null && typeof msg === 'object') { while (lvl-- && typeof argsCloned[0] === 'object') { @@ -362,17 +373,6 @@ function asObject (logger, level, args, ts, opts) { const formattedLogObject = logObjectFormatter(logObject) return [formattedLogObject, ...argsCloned] } else { - if (ts) { - logObject.time = ts - } - - if (levelFormatter) { - const formattedLevel = levelFormatter(level, logger.levels.values[level]) - Object.assign(logObject, formattedLevel) - } else { - logObject.level = logger.levels.values[level] - } - // deliberate, catching objects, arrays if (msg !== null && typeof msg === 'object') { while (lvl-- && typeof argsCloned[0] === 'object') { diff --git a/test/browser.test.js b/test/browser.test.js index 8684006b4..0712e48da 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -165,6 +165,7 @@ test('opts.browser.asObject logs pino-like object to console', ({ end, ok, is }) instance.info('test') end() }) + test('opts.browser.asObject uses opts.messageKey in logs', ({ end, ok, is }) => { const messageKey = 'message' const instance = require('../browser')({ @@ -183,6 +184,25 @@ test('opts.browser.asObject uses opts.messageKey in logs', ({ end, ok, is }) => end() }) +test('opts.browser.asObjectBindingsOnly passes the bindings but keep the message unformatted', ({ end, ok, is, deepEqual }) => { + const messageKey = 'message' + const instance = require('../browser')({ + messageKey, + browser: { + asObjectBindingsOnly: true, + write: function (o, msg, ...args) { + is(o.level, 30) + ok(o.time) + is(msg, 'test %s') + deepEqual(args, ['foo']) + } + } + }) + + instance.info('test %s', 'foo') + end() +}) + test('opts.browser.formatters (level) logs pino-like object to console', ({ end, ok, is }) => { const info = console.info console.info = function (o) { From a19f37c820076bb2b24e8d7d2b63bbc365e8acb5 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Thu, 30 Jan 2025 11:58:25 +0200 Subject: [PATCH 3/3] Add docs --- docs/browser.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/browser.md b/docs/browser.md index a23ce8373..9578e0cea 100644 --- a/docs/browser.md +++ b/docs/browser.md @@ -27,6 +27,21 @@ pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: } When `write` is set, `asObject` will always be `true`. +### `asObjectBindingsOnly` (Boolean) + +```js +const pino = require('pino')({browser: {asObjectBindingsOnly: true}}) +``` + +The `asObjectBindingsOnly` is similar to `asObject` but will keep the message +and arguments unformatted, this allows to defer formatting the message to the +actual call to `console` methods, where browsers then have richer formatting in +their devtools then when pino will format the message to a string first. + +```js +pino.info('hello %s', 'world') // creates and logs {level: 30, time: }, 'hello %s', 'world' +``` + ### `formatters` (Object) An object containing functions for formatting the shape of the log lines. When provided, it enables the logger to produce a pino-like log object with customized formatting. Currently, it supports formatting for the `level` object only.