From 0ef7b809aec24fc75f40242227a57fecf0ae9d28 Mon Sep 17 00:00:00 2001 From: Alberto Delgado Roda Date: Mon, 18 Sep 2023 16:24:24 +0200 Subject: [PATCH] feat(rum-core): stringify rejected obj --- .../src/error-logging/error-logging.js | 40 ++++++++++++++----- .../test/error-logging/error-logging.spec.js | 26 ++++++++++-- packages/rum/src/index.js | 1 + 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/packages/rum-core/src/error-logging/error-logging.js b/packages/rum-core/src/error-logging/error-logging.js index e149e516b..5f4a5ffc5 100644 --- a/packages/rum-core/src/error-logging/error-logging.js +++ b/packages/rum-core/src/error-logging/error-logging.js @@ -33,6 +33,7 @@ import stackParser from 'error-stack-parser' * List of keys to be ignored from getting added to custom error properties */ const IGNORE_KEYS = ['stack', 'message'] +const PROMISE_REJECTION_PREFIX = 'Unhandled promise rejection: ' function getErrorProperties(error) { /** @@ -172,7 +173,6 @@ class ErrorLogging { } logPromiseEvent(promiseRejectionEvent) { - const prefix = 'Unhandled promise rejection: ' let { reason } = promiseRejectionEvent if (reason == null) { reason = '' @@ -186,18 +186,10 @@ class ErrorLogging { const name = reason.name ? reason.name + ': ' : '' errorEvent = { error: reason, - message: prefix + name + reason.message + message: PROMISE_REJECTION_PREFIX + name + reason.message } } else { - reason = - typeof reason === 'object' - ? '' - : typeof reason === 'function' - ? '' - : reason - errorEvent = { - message: prefix + reason - } + errorEvent = this._parseRejectReason(reason) } this.logErrorEvent(errorEvent) } @@ -211,6 +203,32 @@ class ErrorLogging { } return this.logErrorEvent(errorEvent) } + + _parseRejectReason(reason) { + const errorEvent = { + message: PROMISE_REJECTION_PREFIX + } + + if (Array.isArray(reason)) { + errorEvent.message += '' + } else if (typeof reason === 'object') { + try { + errorEvent.message += JSON.stringify(reason) + errorEvent.error = reason + } catch (error) { + // fallback. JSON.stringify can throw exceptions in different circumstances. + // please, see this link for more info: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#exceptions + errorEvent.message += '' + } + } else if (typeof reason === 'function') { + errorEvent.message += '' + } else { + errorEvent.message += reason + } + + return errorEvent + } } export default ErrorLogging diff --git a/packages/rum-core/test/error-logging/error-logging.spec.js b/packages/rum-core/test/error-logging/error-logging.spec.js index 6546210ea..758d78b46 100644 --- a/packages/rum-core/test/error-logging/error-logging.spec.js +++ b/packages/rum-core/test/error-logging/error-logging.spec.js @@ -393,24 +393,44 @@ describe('ErrorLogging', function () { ) errorLogging.logPromiseEvent({ - reason: [{ a: '1' }] + reason: ['array-value'] }) expect(getEvents()[7][ERRORS].exception.message).toBe( 'Unhandled promise rejection: ' ) + errorLogging.logPromiseEvent({ + reason: { a: '1' } + }) + expect(getEvents()[8][ERRORS].exception.message).toBe( + 'Unhandled promise rejection: {"a":"1"}' + ) + + // Make sure that the object fallback case works + // Circular objects causes JSON.stringify to fail + const circularObj = { + foo: 'bar' + } + circularObj.self = circularObj + errorLogging.logPromiseEvent({ + reason: circularObj + }) + expect(getEvents()[9][ERRORS].exception.message).toBe( + 'Unhandled promise rejection: ' + ) + const noop = function () {} errorLogging.logPromiseEvent({ reason: noop }) - expect(getEvents()[8][ERRORS].exception.message).toBe( + expect(getEvents()[10][ERRORS].exception.message).toBe( 'Unhandled promise rejection: ' ) errorLogging.logPromiseEvent({ reason: null }) - expect(getEvents()[9][ERRORS].exception.message).toBe( + expect(getEvents()[11][ERRORS].exception.message).toBe( 'Unhandled promise rejection: ' ) diff --git a/packages/rum/src/index.js b/packages/rum/src/index.js index 1f39bfe96..db501fb49 100644 --- a/packages/rum/src/index.js +++ b/packages/rum/src/index.js @@ -50,6 +50,7 @@ function getApmBase() { } const apmBase = getApmBase() + const init = apmBase.init.bind(apmBase) export default init