diff --git a/lib/bunyan.js b/lib/bunyan.js index 405cdb24..cfd566b1 100644 --- a/lib/bunyan.js +++ b/lib/bunyan.js @@ -1200,22 +1200,48 @@ function safeCyclesArray() { var safeCycles = typeof (Set) !== 'undefined' ? safeCyclesSet : safeCyclesArray; /** - * A fast JSON.stringify that handles cycles and getter exceptions (when - * safeJsonStringify is installed). + * A JSON stringifier that handles bigints safely + * + * Usage: JSON.stringify(obj, replaceBigInt) + */ +function replaceBigInt(key, value) { + if (typeof (value) === 'bigint') { + return value.toString() + 'n'; + } + return value; +} + +/** + * Compose multiple replacer functions together for use with JSON.stringify. + * + * Usage: JSON.stringify(obj, composeReplacer(replaceBigInt, safeCycles())) + */ +function composeReplacer(replacer1, replacer2) { + return function (key, value) { + return replacer1(key, replacer2(key, value)) + } +} + +/** + * A fast JSON.stringify that handles bigints, cycles and getter exceptions + * (when safeJsonStringify is installed). * * This function attempts to use the regular JSON.stringify for speed, but on * error (e.g. JSON cycle detection exception) it falls back to safe stringify - * handlers that can deal with cycles and/or getter exceptions. + * handlers that can deal with bigints, cycles and/or getter exceptions. */ function fastAndSafeJsonStringify(rec) { try { return JSON.stringify(rec); } catch (ex) { try { - return JSON.stringify(rec, safeCycles()); + return JSON.stringify( + rec, + composeReplacer(safeCycles(), replaceBigInt) + ); } catch (e) { if (safeJsonStringify) { - return safeJsonStringify(rec); + return safeJsonStringify(rec, replaceBigInt); } else { var dedupKey = e.stack.split(/\n/g, 3).join('\n'); _warn('bunyan: ERROR: Exception in ' @@ -1231,7 +1257,6 @@ function fastAndSafeJsonStringify(rec) { } } - var RotatingFileStream = null; if (mv) { diff --git a/test/bigint.test.js b/test/bigint.test.js new file mode 100644 index 00000000..b881ceda --- /dev/null +++ b/test/bigint.test.js @@ -0,0 +1,84 @@ +/* + * Make sure bigints are safe + */ + +var Logger = require('../lib/bunyan.js'); +var test = require('tap').test; + + +var Stream = require('stream').Stream; +var outstr = new Stream; +outstr.writable = true; +var output = []; +outstr.write = function (c) { + output.push(JSON.parse(c + '')); +}; +outstr.end = function (c) { + if (c) this.write(c); + this.emit('end'); +}; + +var expect = + [ + { + 'name': 'bigint', + 'level': 30, + 'msg': 'amount 100n', + 'v': 0 + }, + { + 'name': 'bigint', + 'level': 30, + 'msg': 'obj { amount: 100n, numAmount: 100 }', + 'v': 0 + }, + { + 'name': 'bigint', + 'level': 30, + 'amount': '100n', + 'numAmount': 100, + 'msg': '', + 'v': 0 + } + ]; + +var log = new Logger({ + name: 'bigint', + streams: [ + { + type: 'stream', + level: 'info', + stream: outstr + } + ] +}); + +// Bigint is only supported in nodejs 10+ +if (Number(process.versions.node.split('.')[0]) >= 10) { + test('bigints', function (t) { + outstr.on('end', function () { + output.forEach(function (o, i) { + // Drop variable parts for comparison. + delete o.hostname; + delete o.pid; + delete o.time; + + t.equal(JSON.stringify(o), JSON.stringify(expect[i]), + 'log record ' + i + ' matches'); + }); + t.end(); + }); + + var amount = BigInt('100'); + var numAmount = 100 + var obj = { amount: amount, numAmount: numAmount }; + + log.info('amount', amount); + log.info('obj', obj); + log.info(obj); + + t.ok('did not throw'); + + outstr.end(); + }) +} \ No newline at end of file