From b3bc106abc929190c47492b1dbb9ccd809bbcc88 Mon Sep 17 00:00:00 2001
From: "Terence D. Honles" <terence@honles.com>
Date: Thu, 14 Jan 2021 17:25:56 -0800
Subject: [PATCH] support logging "duck typed" Error objects

In TypeScript Error objects might be implemented instead of extended
(i.e. HttpErrorResponse [1]). This causes the `instanceof` check in
createItem to fail to recognize the argument as an error, and this
change fixes that.

[1]: https://github.com/angular/angular/blob/9a5ac47331faf0f6030bff75ad8ef8dab2f8d2d9/packages/common/http/src/response.ts#L319
---
 src/server/rollbar.js |  2 +-
 src/utility.js        | 10 ++++++----
 test/utility.test.js  | 32 ++++++++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 5 deletions(-)

diff --git a/src/server/rollbar.js b/src/server/rollbar.js
index 55af8e1b2..947e8c385 100644
--- a/src/server/rollbar.js
+++ b/src/server/rollbar.js
@@ -280,7 +280,7 @@ Rollbar.prototype.errorHandler = function () {
       return next(err, request, response);
     }
 
-    if (err instanceof Error) {
+    if (_.isError(err)) {
       return this.error(err, request, cb);
     }
     return this.error('Error: ' + err, request, cb);
diff --git a/src/utility.js b/src/utility.js
index e64057cbb..0850bdd0f 100644
--- a/src/utility.js
+++ b/src/utility.js
@@ -139,8 +139,10 @@ function isIterable(i) {
  * @returns true if e is an error
  */
 function isError(e) {
-  // Detect both Error and Firefox Exception type
-  return isType(e, 'error') || isType(e, 'exception');
+  // Detect Error, Firefox Exception type and error like object
+  return (isType(e, 'error')
+    || isType(e, 'exception')
+    || (typeof e === 'object' && e && e.name && e.message));
 }
 
 function redact() {
@@ -410,7 +412,7 @@ function createItem(args, logger, notifier, requestKeys, lambdaContext) {
         break;
       case 'object':
       case 'array':
-        if (arg instanceof Error || (typeof DOMException !== 'undefined' && arg instanceof DOMException)) {
+        if (isError(arg)) {
           err ? extraArgs.push(arg) : err = arg;
           break;
         }
@@ -428,7 +430,7 @@ function createItem(args, logger, notifier, requestKeys, lambdaContext) {
         custom ? extraArgs.push(arg) : custom = arg;
         break;
       default:
-        if (arg instanceof Error || (typeof DOMException !== 'undefined' && arg instanceof DOMException)) {
+        if (isError(arg)) {
           err ? extraArgs.push(arg) : err = arg;
           break;
         }
diff --git a/test/utility.test.js b/test/utility.test.js
index 2e7158efd..e839876e8 100644
--- a/test/utility.test.js
+++ b/test/utility.test.js
@@ -244,6 +244,38 @@ describe('isError', function() {
     expect(_.isError(e)).to.be.ok();
     done();
   });
+  it('should handle classes implementing the error interface', function(done) {
+    // This is a mostly browser compliant way of doing this
+    // just for the sake of doing it, even though we mostly
+    // need this to work in node environments
+    function TestCustomError(message) {
+      Object.defineProperty(this, 'name', {
+        enumerable: false,
+        writable: false,
+        value: 'TestCustomError'
+      });
+
+      Object.defineProperty(this, 'message', {
+        enumerable: false,
+        writable: true,
+        value: message
+      });
+
+      if (Error.hasOwnProperty('captureStackTrace')) {
+        Error.captureStackTrace(this, TestCustomError);
+      } else {
+        Object.defineProperty(this, 'stack', {
+          enumerable: false,
+          writable: false,
+          value: (new Error(message)).stack
+        });
+      }
+    }
+
+    var e = new TestCustomError('bork');
+    expect(_.isError(e)).to.be.ok();
+    done();
+  });
 });
 
 describe('merge', function() {