From 5eea8b6b60a7a6a650736e55a23f583a095ec07b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Mar 2025 17:43:06 +0000 Subject: [PATCH 1/2] Improve debug display of error objects --- .../editor-client/src/js/ui/utils.js | 1 - .../node_modules/@node-red/util/lib/util.js | 44 ++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index 2de4b1131..4529a6923 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -1285,7 +1285,6 @@ RED.utils = (function() { payload = JSON.parse(payload); } else if (/error/i.test(format)) { payload = JSON.parse(payload); - payload = (payload.name?payload.name+": ":"")+payload.message; } else if (format === 'null') { payload = null; } else if (format === 'undefined') { diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index 9ebb46bc0..7bfe41d7b 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -828,18 +828,25 @@ function encodeObject(msg,opts) { debuglength = opts.maxLength; } var msgType = typeof msg.msg; - if (msg.msg instanceof Error) { + if (msg.msg instanceof Error || /Error/.test(msg.msg?.__proto__?.name)) { msg.format = "error"; - var errorMsg = {}; - if (msg.msg.name) { - errorMsg.name = msg.msg.name; + + const cause = msg.msg.cause + const value = { + __enc__: true, + type: 'error', + data: { + name: msg.msg.name, + message: msg.msg.message, + cause: cause + "", + stack: msg.msg.stack, + } } - if (hasOwnProperty.call(msg.msg, 'message')) { - errorMsg.message = msg.msg.message; - } else { - errorMsg.message = msg.msg.toString(); + // Remove cause if not defined + if (!cause) { + delete value.data.cause } - msg.msg = JSON.stringify(errorMsg); + msg.msg = JSON.stringify(value); } else if (msg.msg instanceof Buffer) { msg.format = "buffer["+msg.msg.length+"]"; msg.msg = msg.msg.toString('hex'); @@ -857,6 +864,7 @@ function encodeObject(msg,opts) { msg.format = "Object"; } if (/error/i.test(msg.format)) { + // TODO: check if this is needed msg.msg = JSON.stringify({ name: msg.msg.name, message: msg.msg.message @@ -904,8 +912,22 @@ function encodeObject(msg,opts) { __enc__: true, type: "internal" } - } else if (value instanceof Error) { - value = value.toString() + } else if (value instanceof Error || /Error/.test(value?.__proto__?.name)) { + const cause = value.cause + value = { + __enc__: true, + type: 'error', + data: { + name: value.name, + message: value.message, + cause: cause + "", + stack: value.stack, + } + } + // Remove cause if not defined + if (!cause) { + delete value.data.cause + } } else if (Array.isArray(value) && value.length > debuglength) { value = { __enc__: true, From 82ba56bffea1c4b57043ec4357c0d76952925972 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 11 Mar 2025 16:07:15 +0000 Subject: [PATCH 2/2] Fix up tests --- .../node_modules/@node-red/util/lib/util.js | 38 +++--- test/nodes/core/common/21-debug_spec.js | 16 ++- test/unit/@node-red/util/lib/util_spec.js | 110 +++++++----------- 3 files changed, 77 insertions(+), 87 deletions(-) diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index 7bfe41d7b..d205700a5 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -837,7 +837,7 @@ function encodeObject(msg,opts) { type: 'error', data: { name: msg.msg.name, - message: msg.msg.message, + message: hasOwnProperty.call(msg.msg, 'message') ? msg.msg.message : msg.msg.toString(), cause: cause + "", stack: msg.msg.stack, } @@ -919,7 +919,7 @@ function encodeObject(msg,opts) { type: 'error', data: { name: value.name, - message: value.message, + message: hasOwnProperty.call(value, 'message') ? value.message : value.toString(), cause: cause + "", stack: value.stack, } @@ -999,8 +999,19 @@ function encodeObject(msg,opts) { return value; }); } else { - try { msg.msg = msg.msg.toString(); } - catch(e) { msg.msg = "[Type not printable]" + util.inspect(msg.msg); } + try { + msg.msg = msg.msg.toString(); + } catch(e) { + msg.msg.format = 'error' + msg.msg = JSON.stringify({ + __enc__: true, + type: 'error', + data: { + message: "[Type not serializable]", + stack: e.stack + } + }) + } } } } else if (msgType === "function") { @@ -1031,17 +1042,14 @@ function encodeObject(msg,opts) { return msg; } catch(e) { msg.format = "error"; - var errorMsg = {}; - if (e.name) { - errorMsg.name = e.name; - } - if (hasOwnProperty.call(e, 'message')) { - errorMsg.message = 'encodeObject Error: ['+e.message + '] Value: '+util.inspect(msg.msg); - } else { - errorMsg.message = 'encodeObject Error: ['+e.toString() + '] Value: '+util.inspect(msg.msg); - } - if (errorMsg.message.length > debuglength) { - errorMsg.message = errorMsg.message.substring(0,debuglength); + const errorMsg = { + __enc__: true, + type: 'error', + data: { + name: e.name, + message: 'encodeObject Error: ' + (hasOwnProperty.call(e, 'message') ? e.message : e.toString()), + stack: e.stack, + } } msg.msg = JSON.stringify(errorMsg); return msg; diff --git a/test/nodes/core/common/21-debug_spec.js b/test/nodes/core/common/21-debug_spec.js index 2931ea1e4..a772608ec 100644 --- a/test/nodes/core/common/21-debug_spec.js +++ b/test/nodes/core/common/21-debug_spec.js @@ -173,9 +173,19 @@ describe('debug node', function() { websocket_test(function() { n1.emit("input", {payload: new Error("oops")}); }, function(msg) { - JSON.parse(msg).should.eql([{ - topic:"debug",data:{id:"n1",msg:'{"name":"Error","message":"oops"}',property:"payload",format:"error",path:"global"} - }]); + const fullMsg = JSON.parse(msg) + fullMsg[0].should.have.property('topic', 'debug') + fullMsg[0].should.have.property('data') + fullMsg[0].data.should.have.property('id', 'n1') + fullMsg[0].data.should.have.property('property', 'payload') + fullMsg[0].data.should.have.property('format', 'error') + fullMsg[0].data.should.have.property('path', 'global') + fullMsg[0].data.should.have.property('msg') + const msgData = JSON.parse(fullMsg[0].data.msg) + msgData.should.have.property('__enc__', true) + msgData.should.have.property('type', 'error') + msgData.data.should.have.property('name', 'Error') + msgData.data.should.have.property('message', 'oops') }, done); }); }); diff --git a/test/unit/@node-red/util/lib/util_spec.js b/test/unit/@node-red/util/lib/util_spec.js index e48e9fc84..3a52939f8 100644 --- a/test/unit/@node-red/util/lib/util_spec.js +++ b/test/unit/@node-red/util/lib/util_spec.js @@ -518,8 +518,8 @@ describe("@node-red/util/util", function() { } function testToString(input,msg,expected) { var result = util.normalisePropertyExpression(input,msg,true); - console.log("+",input); - console.log(result); + // console.log("+",input); + // console.log(result); result.should.eql(expected); } it('pass a.b.c',function() { testABC('a.b.c',['a','b','c']); }) @@ -784,9 +784,14 @@ describe("@node-red/util/util", function() { var result = util.encodeObject(msg); result.format.should.eql("error"); var resultJson = JSON.parse(result.msg); - resultJson.name.should.eql('encodeError'); - resultJson.message.should.eql('encode error'); + resultJson.should.have.property("__enc__",true); + resultJson.should.have.property("type","error"); + resultJson.should.have.property("data"); + resultJson.data.should.have.property("name","encodeError") + resultJson.data.should.have.property("message","encode error") + resultJson.data.should.have.property("stack") }); + it('encodes Error without message', function() { var err = new Error(); err.name = 'encodeError'; @@ -795,8 +800,12 @@ describe("@node-red/util/util", function() { var result = util.encodeObject(msg); result.format.should.eql("error"); var resultJson = JSON.parse(result.msg); - resultJson.name.should.eql('encodeError'); - resultJson.message.should.eql('error message'); + resultJson.should.have.property("__enc__",true); + resultJson.should.have.property("type","error"); + resultJson.should.have.property("data"); + resultJson.data.should.have.property("name","encodeError") + resultJson.data.should.have.property("message","error message") + resultJson.data.should.have.property("stack") }); it('encodes Buffer', function() { var msg = {msg:Buffer.from("abc")}; @@ -988,7 +997,13 @@ describe("@node-red/util/util", function() { var result = util.encodeObject(msg); result.format.should.eql("array[1]"); var resultJson = JSON.parse(result.msg); - resultJson[0].should.eql('Error: encode error'); + resultJson[0].should.have.property("__enc__",true); + resultJson[0].should.have.property("type","error"); + resultJson[0].should.have.property("data"); + resultJson[0].data.should.have.property("name","Error") + resultJson[0].data.should.have.property("message","encode error") + resultJson[0].data.should.have.property("stack") + }); it('long array in msg', function() { var msg = {msg:{array:[1,2,3,4]}}; @@ -1074,7 +1089,7 @@ describe("@node-red/util/util", function() { var resultJson = JSON.parse(result.msg); resultJson.socket.should.eql('[internal]'); }); - it('object which fails to serialise', function(done) { + it('object which fails to serialise', function() { var msg = { msg: { obj:{ @@ -1093,13 +1108,13 @@ describe("@node-red/util/util", function() { }; var result = util.encodeObject(msg); result.format.should.eql("error"); - var success = (result.msg.indexOf('cantserialise') > 0); - success &= (result.msg.indexOf('this exception should have been caught') > 0); - success &= (result.msg.indexOf('canserialise') > 0); - success.should.eql(1); - done(); + const resultJson = JSON.parse(result.msg); + resultJson.should.have.property("__enc__",true); + resultJson.should.have.property("type","error"); + resultJson.should.have.property("data"); + resultJson.data.should.have.property("message","encodeObject Error: this exception should have been caught") }); - it('object which fails to serialise - different error type', function(done) { + it('object which fails to serialise - different error type', function() { var msg = { msg: { obj:{ @@ -1116,45 +1131,15 @@ describe("@node-red/util/util", function() { }, } }; - var result = util.encodeObject(msg); + const result = util.encodeObject(msg); result.format.should.eql("error"); - var success = (result.msg.indexOf('cantserialise') > 0); - success &= (result.msg.indexOf('this exception should have been caught') > 0); - success &= (result.msg.indexOf('canserialise') > 0); - success.should.eql(1); - done(); + const resultJson = JSON.parse(result.msg); + resultJson.should.have.property("__enc__",true); + resultJson.should.have.property("type","error"); + resultJson.should.have.property("data"); + resultJson.data.should.have.property("message","encodeObject Error: this exception should have been caught") }); - it('very large object which fails to serialise should be truncated', function(done) { - var msg = { - msg: { - obj:{ - big:"", - cantserialise:{ - message:'this will not be displayed', - toJSON: function(val) { - throw new Error('this exception should have been caught'); - return 'should not display because we threw first'; - }, - }, - canserialise:{ - message:'this should be displayed', - } - }, - } - }; - - for (var i = 0; i < 1000; i++) { - msg.msg.obj.big += 'some more string '; - } - - var result = util.encodeObject(msg); - result.format.should.eql("error"); - var resultJson = JSON.parse(result.msg); - var success = (resultJson.message.length <= 1000); - success.should.eql(true); - done(); - }); - it('test bad toString', function(done) { + it('test bad toString', function() { var msg = { msg: { mystrangeobj:"hello", @@ -1166,25 +1151,12 @@ describe("@node-red/util/util", function() { msg.msg.constructor = { name: "strangeobj" }; var result = util.encodeObject(msg); - var success = (result.msg.indexOf('[Type not printable]') >= 0); - success.should.eql(true); - done(); + const resultJson = JSON.parse(result.msg); + resultJson.should.have.property("__enc__",true); + resultJson.should.have.property("type","error"); + resultJson.should.have.property("data"); + resultJson.data.should.have.property("message","[Type not serializable]") }); - it('test bad object constructor', function(done) { - var msg = { - msg: { - mystrangeobj:"hello", - constructor: { - get name(){ - throw new Error('Exception in constructor name'); - } - } - }, - }; - var result = util.encodeObject(msg); - done(); - }); - }); });