JSON.stringify safely

This commit is contained in:
isaacs 2012-09-10 11:24:39 -07:00
parent 25fe445cff
commit c2338b117c
2 changed files with 93 additions and 19 deletions

View file

@ -69,7 +69,7 @@ if (!format) {
switch (x) { switch (x) {
case '%s': return String(args[i++]); case '%s': return String(args[i++]);
case '%d': return Number(args[i++]); case '%d': return Number(args[i++]);
case '%j': return JSON.stringify(args[i++]); case '%j': return JSON.stringify(args[i++], safeCycles());
case '%%': return '%'; case '%%': return '%';
default: default:
return x; return x;
@ -672,24 +672,7 @@ Logger.prototype._emit = function (rec) {
// Stringify the object. Attempt to warn/recover on error. // Stringify the object. Attempt to warn/recover on error.
var str; var str;
if (this.haveNonRawStreams) { if (this.haveNonRawStreams) {
try { str = JSON.stringify(obj, safeCycles()) + '\n';
str = JSON.stringify(obj) + '\n';
} catch (e) {
var src = ((obj.src && obj.src.file) ? obj.src : getCaller3Info());
var emsg = format('bunyan: ERROR: could not stringify log record from '
+ '%s:%d: %s', src.file, src.line, e);
var eobj = objCopy(rec[0]);
eobj.bunyanMsg = emsg;
eobj.msg = obj.msg;
eobj.time = obj.time;
eobj.v = LOG_VERSION;
_warn(emsg, src.file, src.line);
try {
str = JSON.stringify(eobj) + '\n';
} catch (e2) {
str = JSON.stringify({bunyanMsg: emsg});
}
}
} }
for (i = 0; i < this.streams.length; i++) { for (i = 0; i < this.streams.length; i++) {
@ -1022,6 +1005,21 @@ var errSerializer = Logger.stdSerializers.err = function err(err) {
return obj; return obj;
}; };
// A JSON stringifier that handles cycles safely.
// Usage: JSON.stringify(obj, safeCycles())
function safeCycles() {
var seen = [];
return function(key, val) {
if (!val || typeof val !== 'object') {
return val;
}
if (seen.indexOf(val) !== -1) {
return '[Circular]';
}
seen.push(val);
return val;
};
}
/** /**
* RingBuffer is a Writable Stream that just stores the last N records in * RingBuffer is a Writable Stream that just stores the last N records in

76
test/cycles.test.js Normal file
View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2012 Trent Mick. All rights reserved.
*
* Make sure cycles are safe.
*/
var test = require('tap').test;
var Logger = require('../lib/bunyan.js');
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');
};
// these are lacking a few fields that will probably never match
var expect =
[
{
"name": "blammo",
"level": 30,
"msg": "bango { bang: 'boom', KABOOM: [Circular] }",
"v": 0
},
{
"name": "blammo",
"level": 30,
"msg": "kaboom { bang: 'boom', KABOOM: [Circular] }",
"v": 0
},
{
"name": "blammo",
"level": 30,
"bang": "boom",
"KABOOM": {
"bang": "boom",
"KABOOM": "[Circular]"
},
"msg": "",
"v": 0
}
];
var log = new Logger({
name: 'blammo',
streams: [
{
type: 'stream',
level: 'info',
stream: outstr
}
]
});
test('cycles', function (t) {
outstr.on('end', function () {
output.forEach(function (o, i) {
t.has(o, expect[i], 'log item ' + i + ' matches');
});
t.end();
});
var obj = { bang: 'boom' };
obj.KABOOM = obj;
log.info('bango', obj);
log.info('kaboom', obj.KABOOM);
log.info(obj);
outstr.end();
t.pass('did not throw');
});