@newrelic/browser-agent 0.0.9 → 0.1.229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -207
- package/dist/cjs/cdn/lite.js +14 -0
- package/dist/cjs/cdn/polyfills/lite.js +4 -0
- package/dist/cjs/cdn/polyfills/pro.js +4 -0
- package/dist/cjs/cdn/polyfills/spa.js +4 -0
- package/dist/cjs/cdn/polyfills.js +14 -0
- package/dist/cjs/cdn/pro.js +19 -0
- package/dist/cjs/cdn/spa.js +19 -0
- package/dist/cjs/cdn/worker.js +16 -0
- package/dist/cjs/common/aggregate/aggregator.js +168 -0
- package/dist/cjs/common/browser-version/firefox-version.js +17 -0
- package/dist/cjs/common/browser-version/ios-version.js +18 -0
- package/dist/cjs/common/config/config.js +76 -0
- package/dist/cjs/common/config/state/configurable.js +32 -0
- package/dist/cjs/common/config/state/info.js +50 -0
- package/dist/cjs/common/config/state/init.js +86 -0
- package/dist/cjs/common/config/state/loader-config.js +28 -0
- package/dist/cjs/common/config/state/originals.js +9 -0
- package/dist/cjs/common/config/state/runtime.js +50 -0
- package/dist/cjs/common/constants/environment-variables.js +20 -0
- package/dist/cjs/common/context/shared-context.js +25 -0
- package/dist/cjs/common/deny-list/deny-list.js +108 -0
- package/dist/cjs/common/drain/drain.js +126 -0
- package/dist/cjs/common/event-emitter/contextual-ee.js +149 -0
- package/dist/cjs/common/event-emitter/handle.js +24 -0
- package/dist/cjs/common/event-emitter/register-handler.js +24 -0
- package/dist/cjs/common/event-listener/event-listener-opts.js +46 -0
- package/dist/cjs/common/harvest/harvest-scheduler.js +111 -0
- package/dist/cjs/common/harvest/harvest.js +236 -0
- package/dist/cjs/common/ids/id.js +30 -0
- package/dist/cjs/common/ids/unique-id.js +84 -0
- package/dist/cjs/common/metrics/framework-detection.js +72 -0
- package/dist/cjs/common/metrics/paint-metrics.js +13 -0
- package/dist/cjs/common/serialize/bel-serializer.js +89 -0
- package/dist/cjs/common/timing/nav-timing.js +77 -0
- package/dist/cjs/common/timing/now.js +15 -0
- package/dist/cjs/common/unload/eol.js +69 -0
- package/dist/cjs/common/url/clean-url.js +16 -0
- package/dist/cjs/common/url/encode.js +79 -0
- package/dist/cjs/common/url/location.js +14 -0
- package/dist/cjs/common/url/parse-url.js +66 -0
- package/dist/cjs/common/url/protocol.js +25 -0
- package/dist/cjs/common/util/console.js +17 -0
- package/dist/cjs/common/util/data-size.js +25 -0
- package/dist/cjs/common/util/feature-flags.js +42 -0
- package/dist/cjs/common/util/get-or-set.js +39 -0
- package/dist/cjs/common/util/global-scope.js +56 -0
- package/dist/cjs/common/util/map-own.js +24 -0
- package/dist/cjs/common/util/obfuscate.js +76 -0
- package/dist/cjs/common/util/reduce.js +22 -0
- package/dist/cjs/common/util/s-hash.js +19 -0
- package/dist/cjs/common/util/single.js +23 -0
- package/dist/cjs/common/util/stringify.js +47 -0
- package/dist/cjs/common/util/submit-data.js +99 -0
- package/dist/cjs/common/util/traverse.js +41 -0
- package/dist/cjs/common/util/user-agent.js +57 -0
- package/dist/cjs/common/window/load.js +19 -0
- package/dist/cjs/common/window/nreum.js +107 -0
- package/dist/cjs/common/window/page-visibility.js +28 -0
- package/dist/cjs/common/window/session-storage.js +42 -0
- package/dist/cjs/common/window/supports-performance-observer.js +15 -0
- package/dist/cjs/common/window/top-level-callers.js +23 -0
- package/dist/cjs/common/wrap/index.js +68 -0
- package/dist/cjs/common/wrap/wrap-events.js +105 -0
- package/dist/cjs/common/wrap/wrap-fetch.js +114 -0
- package/dist/cjs/common/wrap/wrap-function.js +269 -0
- package/dist/cjs/common/wrap/wrap-history.js +56 -0
- package/dist/cjs/common/wrap/wrap-jsonp.js +129 -0
- package/dist/cjs/common/wrap/wrap-mutation.js +61 -0
- package/dist/cjs/common/wrap/wrap-promise.js +160 -0
- package/dist/cjs/common/wrap/wrap-raf.js +55 -0
- package/dist/cjs/common/wrap/wrap-timer.js +70 -0
- package/dist/cjs/common/wrap/wrap-xhr.js +206 -0
- package/dist/cjs/features/ajax/aggregate/index.js +226 -0
- package/dist/cjs/features/ajax/constants.js +9 -0
- package/dist/cjs/features/ajax/index.js +12 -0
- package/dist/cjs/features/ajax/instrument/distributed-tracing.js +145 -0
- package/dist/cjs/features/ajax/instrument/index.js +338 -0
- package/dist/cjs/features/ajax/instrument/response-size.js +26 -0
- package/dist/cjs/features/jserrors/aggregate/canonical-function-name.js +18 -0
- package/dist/cjs/features/jserrors/aggregate/canonical-function-name.test.js +30 -0
- package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +216 -0
- package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.test.js +257 -0
- package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +36 -0
- package/dist/cjs/features/jserrors/aggregate/format-stack-trace.test.js +39 -0
- package/dist/cjs/features/jserrors/aggregate/index.js +267 -0
- package/dist/cjs/features/jserrors/aggregate/string-hash-code.js +23 -0
- package/dist/cjs/features/jserrors/aggregate/string-hash-code.test.js +26 -0
- package/dist/cjs/features/jserrors/constants.js +11 -0
- package/dist/cjs/features/jserrors/index.js +12 -0
- package/dist/cjs/features/jserrors/instrument/debug.js +40 -0
- package/dist/cjs/features/jserrors/instrument/index.js +158 -0
- package/dist/cjs/features/metrics/aggregate/index.js +136 -0
- package/dist/cjs/features/metrics/constants.js +17 -0
- package/dist/cjs/features/metrics/index.js +12 -0
- package/dist/cjs/features/metrics/instrument/index.js +20 -0
- package/dist/cjs/features/metrics/instrument/workers-helper.js +121 -0
- package/dist/cjs/features/page_action/aggregate/index.js +112 -0
- package/dist/cjs/features/page_action/constants.js +9 -0
- package/dist/cjs/features/page_action/index.js +12 -0
- package/dist/cjs/features/page_action/instrument/index.js +21 -0
- package/dist/cjs/features/page_view_event/aggregate/index.js +133 -0
- package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +39 -0
- package/dist/cjs/features/page_view_event/constants.js +15 -0
- package/dist/cjs/features/page_view_event/index.js +12 -0
- package/dist/cjs/features/page_view_event/instrument/index.js +38 -0
- package/dist/cjs/features/page_view_timing/aggregate/index.js +265 -0
- package/dist/cjs/features/page_view_timing/constants.js +9 -0
- package/dist/cjs/features/page_view_timing/first-paint.js +50 -0
- package/dist/cjs/features/page_view_timing/index.js +12 -0
- package/dist/cjs/features/page_view_timing/instrument/index.js +36 -0
- package/dist/cjs/features/page_view_timing/long-tasks.js +75 -0
- package/dist/cjs/features/session_trace/aggregate/index.js +375 -0
- package/dist/cjs/features/session_trace/constants.js +32 -0
- package/dist/cjs/features/session_trace/index.js +12 -0
- package/dist/cjs/features/session_trace/instrument/index.js +133 -0
- package/dist/cjs/features/spa/aggregate/index.js +684 -0
- package/dist/cjs/features/spa/aggregate/interaction-node.js +84 -0
- package/dist/cjs/features/spa/aggregate/interaction-node.test.js +16 -0
- package/dist/cjs/features/spa/aggregate/interaction.js +98 -0
- package/dist/cjs/features/spa/aggregate/serializer.js +147 -0
- package/dist/cjs/features/spa/constants.js +53 -0
- package/dist/cjs/features/spa/index.js +12 -0
- package/dist/cjs/features/spa/instrument/index.js +114 -0
- package/dist/cjs/features/utils/aggregate-base.js +13 -0
- package/dist/cjs/features/utils/feature-base.js +58 -0
- package/dist/cjs/features/utils/handler-cache.js +64 -0
- package/dist/cjs/features/utils/instrument-base.js +71 -0
- package/dist/cjs/features/utils/lazy-loader.js +44 -0
- package/dist/cjs/index.js +81 -58
- package/dist/cjs/loaders/agent.js +86 -0
- package/dist/cjs/loaders/api/api.js +109 -0
- package/dist/cjs/loaders/api/apiAsync.js +94 -0
- package/dist/cjs/loaders/browser-agent.js +29 -0
- package/dist/cjs/loaders/configure/configure.js +47 -0
- package/dist/cjs/loaders/features/enabled-features.js +19 -0
- package/dist/cjs/loaders/features/featureDependencies.js +32 -0
- package/dist/cjs/loaders/features/features.js +33 -0
- package/dist/cjs/loaders/micro-agent.js +93 -0
- package/dist/cjs/loaders/worker-agent.js +24 -0
- package/dist/esm/cdn/lite.js +12 -0
- package/dist/esm/cdn/polyfills/lite.js +7 -0
- package/dist/esm/cdn/polyfills/pro.js +7 -0
- package/dist/esm/cdn/polyfills/spa.js +7 -0
- package/dist/esm/cdn/polyfills.js +17 -0
- package/dist/esm/cdn/pro.js +17 -0
- package/dist/esm/cdn/spa.js +17 -0
- package/dist/esm/cdn/worker.js +14 -0
- package/dist/esm/common/aggregate/aggregator.js +161 -0
- package/dist/esm/common/browser-version/firefox-version.js +10 -0
- package/dist/esm/common/browser-version/ios-version.js +10 -0
- package/dist/esm/common/config/config.js +11 -0
- package/dist/esm/common/config/state/configurable.js +25 -0
- package/dist/esm/common/config/state/info.js +42 -0
- package/dist/esm/common/config/state/init.js +78 -0
- package/dist/esm/common/config/state/loader-config.js +21 -0
- package/dist/esm/common/config/state/originals.js +2 -0
- package/dist/esm/common/config/state/runtime.js +41 -0
- package/dist/esm/common/constants/environment-variables.js +11 -0
- package/dist/esm/common/context/shared-context.js +18 -0
- package/dist/esm/common/deny-list/deny-list.js +101 -0
- package/dist/esm/common/drain/drain.js +119 -0
- package/dist/esm/common/event-emitter/contextual-ee.js +142 -0
- package/dist/esm/common/event-emitter/handle.js +16 -0
- package/dist/esm/common/event-emitter/register-handler.js +19 -0
- package/dist/esm/common/event-listener/event-listener-opts.js +39 -0
- package/dist/esm/common/harvest/harvest-scheduler.js +105 -0
- package/dist/esm/common/harvest/harvest.js +228 -0
- package/dist/esm/common/ids/id.js +23 -0
- package/dist/esm/common/ids/unique-id.js +75 -0
- package/dist/esm/common/metrics/framework-detection.js +66 -0
- package/dist/esm/common/metrics/paint-metrics.js +6 -0
- package/dist/esm/common/serialize/bel-serializer.js +80 -0
- package/dist/esm/common/timing/nav-timing.js +67 -0
- package/dist/esm/common/timing/now.js +9 -0
- package/dist/esm/common/unload/eol.js +62 -0
- package/dist/esm/common/url/clean-url.js +10 -0
- package/dist/esm/common/url/encode.js +71 -0
- package/dist/esm/common/url/location.js +8 -0
- package/dist/esm/common/url/parse-url.js +60 -0
- package/dist/esm/common/url/protocol.js +17 -0
- package/dist/esm/common/util/console.js +11 -0
- package/dist/esm/common/util/data-size.js +19 -0
- package/dist/esm/common/util/feature-flags.js +33 -0
- package/dist/esm/common/util/get-or-set.js +33 -0
- package/dist/esm/common/util/global-scope.js +44 -0
- package/dist/esm/common/util/map-own.js +18 -0
- package/dist/esm/common/util/obfuscate.js +67 -0
- package/dist/esm/common/util/reduce.js +16 -0
- package/dist/esm/common/util/s-hash.js +13 -0
- package/dist/esm/common/util/single.js +16 -0
- package/dist/esm/common/util/stringify.js +42 -0
- package/dist/esm/common/util/submit-data.js +91 -0
- package/dist/esm/common/util/traverse.js +35 -0
- package/dist/esm/common/util/user-agent.js +48 -0
- package/dist/esm/common/window/load.js +12 -0
- package/dist/esm/common/window/nreum.js +91 -0
- package/dist/esm/common/window/page-visibility.js +23 -0
- package/dist/esm/common/window/session-storage.js +36 -0
- package/dist/esm/common/window/supports-performance-observer.js +9 -0
- package/dist/esm/common/window/top-level-callers.js +17 -0
- package/dist/esm/common/wrap/index.js +14 -0
- package/dist/esm/common/wrap/wrap-events.js +97 -0
- package/dist/esm/common/wrap/wrap-fetch.js +105 -0
- package/dist/esm/common/wrap/wrap-function.js +257 -0
- package/dist/esm/common/wrap/wrap-history.js +48 -0
- package/dist/esm/common/wrap/wrap-jsonp.js +122 -0
- package/dist/esm/common/wrap/wrap-mutation.js +54 -0
- package/dist/esm/common/wrap/wrap-promise.js +153 -0
- package/dist/esm/common/wrap/wrap-raf.js +48 -0
- package/dist/esm/common/wrap/wrap-timer.js +63 -0
- package/dist/esm/common/wrap/wrap-xhr.js +199 -0
- package/dist/esm/features/ajax/aggregate/index.js +218 -0
- package/dist/esm/features/ajax/constants.js +2 -0
- package/dist/esm/features/ajax/index.js +1 -0
- package/dist/esm/features/ajax/instrument/distributed-tracing.js +137 -0
- package/dist/esm/features/ajax/instrument/index.js +330 -0
- package/dist/esm/features/ajax/instrument/response-size.js +19 -0
- package/dist/esm/features/jserrors/aggregate/canonical-function-name.js +12 -0
- package/dist/esm/features/jserrors/aggregate/canonical-function-name.test.js +28 -0
- package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +209 -0
- package/dist/esm/features/jserrors/aggregate/compute-stack-trace.test.js +255 -0
- package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +29 -0
- package/dist/esm/features/jserrors/aggregate/format-stack-trace.test.js +37 -0
- package/dist/esm/features/jserrors/aggregate/index.js +260 -0
- package/dist/esm/features/jserrors/aggregate/string-hash-code.js +17 -0
- package/dist/esm/features/jserrors/aggregate/string-hash-code.test.js +24 -0
- package/dist/esm/features/jserrors/constants.js +3 -0
- package/dist/esm/features/jserrors/index.js +1 -0
- package/dist/esm/features/jserrors/instrument/debug.js +38 -0
- package/dist/esm/features/jserrors/instrument/index.js +150 -0
- package/dist/esm/features/metrics/aggregate/index.js +129 -0
- package/dist/esm/features/metrics/constants.js +6 -0
- package/dist/esm/features/metrics/index.js +1 -0
- package/dist/esm/features/metrics/instrument/index.js +13 -0
- package/dist/esm/features/metrics/instrument/workers-helper.js +116 -0
- package/dist/esm/features/page_action/aggregate/index.js +105 -0
- package/dist/esm/features/page_action/constants.js +2 -0
- package/dist/esm/features/page_action/index.js +1 -0
- package/dist/esm/features/page_action/instrument/index.js +14 -0
- package/dist/esm/features/page_view_event/aggregate/index.js +124 -0
- package/dist/esm/features/page_view_event/aggregate/initialized-features.js +34 -0
- package/dist/esm/features/page_view_event/constants.js +5 -0
- package/dist/esm/features/page_view_event/index.js +1 -0
- package/dist/esm/features/page_view_event/instrument/index.js +29 -0
- package/dist/esm/features/page_view_timing/aggregate/index.js +258 -0
- package/dist/esm/features/page_view_timing/constants.js +2 -0
- package/dist/esm/features/page_view_timing/first-paint.js +43 -0
- package/dist/esm/features/page_view_timing/index.js +1 -0
- package/dist/esm/features/page_view_timing/instrument/index.js +28 -0
- package/dist/esm/features/page_view_timing/long-tasks.js +69 -0
- package/dist/esm/features/session_trace/aggregate/index.js +366 -0
- package/dist/esm/features/session_trace/constants.js +14 -0
- package/dist/esm/features/session_trace/index.js +1 -0
- package/dist/esm/features/session_trace/instrument/index.js +123 -0
- package/dist/esm/features/spa/aggregate/index.js +674 -0
- package/dist/esm/features/spa/aggregate/interaction-node.js +78 -0
- package/dist/esm/features/spa/aggregate/interaction-node.test.js +14 -0
- package/dist/esm/features/spa/aggregate/interaction.js +92 -0
- package/dist/esm/features/spa/aggregate/serializer.js +139 -0
- package/dist/esm/features/spa/constants.js +25 -0
- package/dist/esm/features/spa/index.js +1 -0
- package/dist/esm/features/spa/instrument/index.js +104 -0
- package/dist/esm/features/utils/aggregate-base.js +6 -0
- package/dist/esm/features/utils/feature-base.js +51 -0
- package/dist/esm/features/utils/handler-cache.js +57 -0
- package/dist/esm/features/utils/instrument-base.js +69 -0
- package/dist/esm/features/utils/lazy-loader.js +37 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/loaders/agent.js +77 -0
- package/dist/esm/loaders/api/api.js +104 -0
- package/dist/esm/loaders/api/apiAsync.js +88 -0
- package/dist/esm/loaders/browser-agent.js +23 -0
- package/dist/esm/loaders/configure/configure.js +41 -0
- package/dist/esm/loaders/features/enabled-features.js +13 -0
- package/dist/esm/loaders/features/featureDependencies.js +25 -0
- package/dist/esm/loaders/features/features.js +25 -0
- package/dist/esm/loaders/micro-agent.js +86 -0
- package/dist/esm/loaders/worker-agent.js +18 -0
- package/package.json +204 -71
- package/types.ts +221 -0
- package/dist/bundled/es5/index.js +0 -2
- package/dist/bundled/es5/index.js.map +0 -1
- package/dist/bundled/es6/index.js +0 -2
- package/dist/bundled/es6/index.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -19
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/types.d.ts +0 -94
- package/dist/cjs/types.js +0 -28
- package/dist/cjs/types.js.map +0 -1
- package/dist/cjs/utils/api/api.d.ts +0 -10
- package/dist/cjs/utils/api/api.js +0 -40
- package/dist/cjs/utils/api/api.js.map +0 -1
- package/dist/cjs/utils/config/build-configs.d.ts +0 -6
- package/dist/cjs/utils/config/build-configs.js +0 -68
- package/dist/cjs/utils/config/build-configs.js.map +0 -1
- package/dist/cjs/utils/features/features.d.ts +0 -5
- package/dist/cjs/utils/features/features.js +0 -14
- package/dist/cjs/utils/features/features.js.map +0 -1
- package/dist/cjs/utils/features/initialize.d.ts +0 -5
- package/dist/cjs/utils/features/initialize.js +0 -51
- package/dist/cjs/utils/features/initialize.js.map +0 -1
- package/dist/es/index.d.ts +0 -19
- package/dist/es/index.js +0 -55
- package/dist/es/index.js.map +0 -1
- package/dist/es/types.d.ts +0 -94
- package/dist/es/types.js +0 -24
- package/dist/es/types.js.map +0 -1
- package/dist/es/utils/api/api.d.ts +0 -10
- package/dist/es/utils/api/api.js +0 -36
- package/dist/es/utils/api/api.js.map +0 -1
- package/dist/es/utils/config/build-configs.d.ts +0 -6
- package/dist/es/utils/config/build-configs.js +0 -64
- package/dist/es/utils/config/build-configs.js.map +0 -1
- package/dist/es/utils/features/features.d.ts +0 -5
- package/dist/es/utils/features/features.js +0 -10
- package/dist/es/utils/features/features.js.map +0 -1
- package/dist/es/utils/features/initialize.d.ts +0 -5
- package/dist/es/utils/features/initialize.js +0 -24
- package/dist/es/utils/features/initialize.js.map +0 -1
- package/dist/umd/index.d.ts +0 -19
- package/dist/umd/index.js +0 -69
- package/dist/umd/index.js.map +0 -1
- package/dist/umd/types.d.ts +0 -94
- package/dist/umd/types.js +0 -38
- package/dist/umd/types.js.map +0 -1
- package/dist/umd/utils/api/api.d.ts +0 -10
- package/dist/umd/utils/api/api.js +0 -50
- package/dist/umd/utils/api/api.js.map +0 -1
- package/dist/umd/utils/config/build-configs.d.ts +0 -6
- package/dist/umd/utils/config/build-configs.js +0 -78
- package/dist/umd/utils/config/build-configs.js.map +0 -1
- package/dist/umd/utils/features/features.d.ts +0 -5
- package/dist/umd/utils/features/features.js +0 -24
- package/dist/umd/utils/features/features.js.map +0 -1
- package/dist/umd/utils/features/initialize.d.ts +0 -5
- package/dist/umd/utils/features/initialize.js +0 -63
- package/dist/umd/utils/features/initialize.js.map +0 -1
- package/dist/webpack-analysis.html +0 -38
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import { browserErrorUtils } from '../../../../tools/testing-utils';
|
|
3
|
+
import { computeStackTrace } from './compute-stack-trace';
|
|
4
|
+
const baseMockError = {
|
|
5
|
+
toString: 'RangeError: Invalid array length',
|
|
6
|
+
name: 'RangeError',
|
|
7
|
+
constructor: 'function RangeError() { [native code] }',
|
|
8
|
+
message: 'Invalid array length',
|
|
9
|
+
stack: 'RangeError: Invalid array length\n at errorTest (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:74:16)\n at captureError (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:17:9)\n at onload (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:70:5)'
|
|
10
|
+
};
|
|
11
|
+
test('parsing should return a failure for a null error object', () => {
|
|
12
|
+
const result = computeStackTrace(null);
|
|
13
|
+
expect(result).toEqual(expect.objectContaining({
|
|
14
|
+
mode: 'failed',
|
|
15
|
+
stackString: '',
|
|
16
|
+
frames: []
|
|
17
|
+
}));
|
|
18
|
+
});
|
|
19
|
+
describe('errors with stack property', () => {
|
|
20
|
+
test('parsed name should be unknown when name and constructor are missing', () => {
|
|
21
|
+
const mockError = browserErrorUtils.constructError({
|
|
22
|
+
...baseMockError,
|
|
23
|
+
name: null,
|
|
24
|
+
constructor: null
|
|
25
|
+
});
|
|
26
|
+
const result = computeStackTrace(mockError);
|
|
27
|
+
expect(result).toEqual(expect.objectContaining({
|
|
28
|
+
mode: 'stack',
|
|
29
|
+
name: 'unknown',
|
|
30
|
+
message: mockError.message,
|
|
31
|
+
stackString: mockError.stack
|
|
32
|
+
}));
|
|
33
|
+
});
|
|
34
|
+
test('parsed stack should not contain nrWrapper', () => {
|
|
35
|
+
const alteredError = baseMockError;
|
|
36
|
+
alteredError.stack += '\n at nrWrapper (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:60:17)';
|
|
37
|
+
const mockError = browserErrorUtils.constructError(alteredError);
|
|
38
|
+
const result = computeStackTrace(mockError);
|
|
39
|
+
expect(result).toEqual(expect.objectContaining({
|
|
40
|
+
mode: 'stack',
|
|
41
|
+
name: mockError.name,
|
|
42
|
+
message: mockError.message,
|
|
43
|
+
stackString: expect.not.stringContaining('nrWrapper')
|
|
44
|
+
}));
|
|
45
|
+
expect(result.frames).not.toContainEqual(expect.objectContaining({
|
|
46
|
+
func: 'nrWrapper'
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
test('stack should still parse when column numbers are missing', () => {
|
|
50
|
+
const mockError = browserErrorUtils.constructError({
|
|
51
|
+
...baseMockError,
|
|
52
|
+
stack: 'Error: Blocked a frame with origin "http://bam-test-1.nr-local.net:3334" from accessing a cross-origin frame.\n at errorTest (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:60)\n at captureError (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:17)\n at onload (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html?loader=spa:57)'
|
|
53
|
+
});
|
|
54
|
+
const result = computeStackTrace(mockError);
|
|
55
|
+
expect(result).toEqual(expect.objectContaining({
|
|
56
|
+
mode: 'stack',
|
|
57
|
+
name: mockError.name,
|
|
58
|
+
message: mockError.message,
|
|
59
|
+
stackString: mockError.stack
|
|
60
|
+
}));
|
|
61
|
+
expect(result.frames.length).toEqual(3);
|
|
62
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
63
|
+
func: 'errorTest',
|
|
64
|
+
column: null
|
|
65
|
+
}));
|
|
66
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
67
|
+
func: 'captureError',
|
|
68
|
+
column: null
|
|
69
|
+
}));
|
|
70
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
71
|
+
func: 'onload',
|
|
72
|
+
column: null
|
|
73
|
+
}));
|
|
74
|
+
});
|
|
75
|
+
test('parser can handle chrome eval stack', () => {
|
|
76
|
+
const mockError = browserErrorUtils.constructError({
|
|
77
|
+
...baseMockError,
|
|
78
|
+
stack: ' at foobar (eval at foobar (http://bam-test-1.nr-local.net:3334/tests/assets/instrumented.html))'
|
|
79
|
+
});
|
|
80
|
+
const result = computeStackTrace(mockError);
|
|
81
|
+
expect(result).toEqual(expect.objectContaining({
|
|
82
|
+
mode: 'stack',
|
|
83
|
+
name: mockError.name,
|
|
84
|
+
message: mockError.message,
|
|
85
|
+
stackString: mockError.stack
|
|
86
|
+
}));
|
|
87
|
+
expect(result.frames.length).toEqual(1);
|
|
88
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
89
|
+
func: 'evaluated code'
|
|
90
|
+
}));
|
|
91
|
+
});
|
|
92
|
+
test('parser can handle ie eval stack', () => {
|
|
93
|
+
const mockError = browserErrorUtils.constructError({
|
|
94
|
+
toString: 'TypeError: Permission denied',
|
|
95
|
+
name: 'TypeError',
|
|
96
|
+
constructor: '\nfunction TypeError() {\n [native code]\n}\n',
|
|
97
|
+
message: 'Permission denied',
|
|
98
|
+
stack: ' at Function code (Function code:23:23)'
|
|
99
|
+
});
|
|
100
|
+
const result = computeStackTrace(mockError);
|
|
101
|
+
expect(result).toEqual(expect.objectContaining({
|
|
102
|
+
mode: 'stack',
|
|
103
|
+
name: mockError.name,
|
|
104
|
+
message: mockError.message,
|
|
105
|
+
stackString: mockError.stack
|
|
106
|
+
}));
|
|
107
|
+
expect(result.frames.length).toEqual(1);
|
|
108
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
109
|
+
func: 'evaluated code'
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
test('parser can handle stack with anonymous function', () => {
|
|
113
|
+
const mockError = browserErrorUtils.constructError({
|
|
114
|
+
...baseMockError,
|
|
115
|
+
stack: 'anonymous'
|
|
116
|
+
});
|
|
117
|
+
const result = computeStackTrace(mockError);
|
|
118
|
+
expect(result).toEqual(expect.objectContaining({
|
|
119
|
+
mode: 'stack',
|
|
120
|
+
name: mockError.name,
|
|
121
|
+
message: mockError.message,
|
|
122
|
+
stackString: mockError.stack
|
|
123
|
+
}));
|
|
124
|
+
expect(result.frames.length).toEqual(1);
|
|
125
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
126
|
+
func: 'evaluated code'
|
|
127
|
+
}));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('errors without stack property and with line property', () => {
|
|
131
|
+
/**
|
|
132
|
+
* @deprecated sourceURL is no longer present in errors for any browsers we support
|
|
133
|
+
*/
|
|
134
|
+
test('parsed stack should contain sourceURL and line number', () => {
|
|
135
|
+
const sourceURL = faker.internet.url();
|
|
136
|
+
const mockError = browserErrorUtils.constructError({
|
|
137
|
+
...baseMockError,
|
|
138
|
+
stack: undefined,
|
|
139
|
+
line: 100,
|
|
140
|
+
sourceURL
|
|
141
|
+
});
|
|
142
|
+
const result = computeStackTrace(mockError);
|
|
143
|
+
expect(result).toEqual(expect.objectContaining({
|
|
144
|
+
mode: 'sourceline',
|
|
145
|
+
name: mockError.name,
|
|
146
|
+
message: mockError.message,
|
|
147
|
+
stackString: `${mockError.name}: ${mockError.message}\n at ${sourceURL}:${mockError.line}`
|
|
148
|
+
}));
|
|
149
|
+
expect(result.frames.length).toEqual(1);
|
|
150
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
151
|
+
url: sourceURL,
|
|
152
|
+
line: mockError.line
|
|
153
|
+
}));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @deprecated sourceURL is no longer present in errors for any browsers we support
|
|
158
|
+
*/
|
|
159
|
+
test('parsed stack should contain sourceURL, line number, and column number', () => {
|
|
160
|
+
const sourceURL = faker.internet.url();
|
|
161
|
+
const mockError = browserErrorUtils.constructError({
|
|
162
|
+
...baseMockError,
|
|
163
|
+
line: 100,
|
|
164
|
+
column: 200,
|
|
165
|
+
stack: undefined,
|
|
166
|
+
sourceURL
|
|
167
|
+
});
|
|
168
|
+
const result = computeStackTrace(mockError);
|
|
169
|
+
expect(result).toEqual(expect.objectContaining({
|
|
170
|
+
mode: 'sourceline',
|
|
171
|
+
name: mockError.name,
|
|
172
|
+
message: mockError.message,
|
|
173
|
+
stackString: `${mockError.name}: ${mockError.message}\n at ${sourceURL}:${mockError.line}:${mockError.column}`
|
|
174
|
+
}));
|
|
175
|
+
expect(result.frames.length).toEqual(1);
|
|
176
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
177
|
+
url: sourceURL,
|
|
178
|
+
line: mockError.line,
|
|
179
|
+
column: mockError.column
|
|
180
|
+
}));
|
|
181
|
+
});
|
|
182
|
+
test('parsed stack should contain "evaluated code" if sourceURL property is not present', () => {
|
|
183
|
+
const mockError = browserErrorUtils.constructError({
|
|
184
|
+
...baseMockError,
|
|
185
|
+
line: 100,
|
|
186
|
+
column: 200,
|
|
187
|
+
stack: undefined
|
|
188
|
+
});
|
|
189
|
+
const result = computeStackTrace(mockError);
|
|
190
|
+
expect(result).toEqual(expect.objectContaining({
|
|
191
|
+
mode: 'sourceline',
|
|
192
|
+
name: mockError.name,
|
|
193
|
+
message: mockError.message,
|
|
194
|
+
stackString: `RangeError: ${mockError.message}\n in evaluated code`
|
|
195
|
+
}));
|
|
196
|
+
expect(result.frames.length).toEqual(1);
|
|
197
|
+
expect(result.frames).toContainEqual(expect.objectContaining({
|
|
198
|
+
func: 'evaluated code'
|
|
199
|
+
}));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// TODO: computeStackTraceBySourceAndLine does not respect firefox lineNumber and columnNumber properties when stack is empty
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* These tests are here because JS allows you to throw absolutely anything as an
|
|
207
|
+
* error, including primitives.
|
|
208
|
+
*/
|
|
209
|
+
describe('errors that are messages only or primitives', () => {
|
|
210
|
+
test('parser should get error name from constructor', () => {
|
|
211
|
+
const mockError = browserErrorUtils.constructError({
|
|
212
|
+
toString: '0',
|
|
213
|
+
constructor: 'function Number() { [native code] }'
|
|
214
|
+
});
|
|
215
|
+
const result = computeStackTrace(mockError);
|
|
216
|
+
expect(result).toEqual(expect.objectContaining({
|
|
217
|
+
mode: 'nameonly',
|
|
218
|
+
name: 'Number',
|
|
219
|
+
stackString: 'Number: undefined',
|
|
220
|
+
frames: []
|
|
221
|
+
}));
|
|
222
|
+
});
|
|
223
|
+
test('parser should get error name from name property', () => {
|
|
224
|
+
const mockError = browserErrorUtils.constructError({
|
|
225
|
+
toString: '0',
|
|
226
|
+
name: faker.datatype.uuid(),
|
|
227
|
+
constructor: 'function Number() { [native code] }'
|
|
228
|
+
});
|
|
229
|
+
const result = computeStackTrace(mockError);
|
|
230
|
+
expect(result).toEqual(expect.objectContaining({
|
|
231
|
+
mode: 'nameonly',
|
|
232
|
+
name: mockError.name,
|
|
233
|
+
stackString: `${mockError.name}: undefined`,
|
|
234
|
+
frames: []
|
|
235
|
+
}));
|
|
236
|
+
});
|
|
237
|
+
test('parser should include the message property', () => {
|
|
238
|
+
const mockError = browserErrorUtils.constructError({
|
|
239
|
+
toString: '0',
|
|
240
|
+
name: faker.datatype.uuid(),
|
|
241
|
+
message: faker.datatype.uuid(),
|
|
242
|
+
constructor: 'function Number() { [native code] }'
|
|
243
|
+
});
|
|
244
|
+
const result = computeStackTrace(mockError);
|
|
245
|
+
expect(result).toEqual(expect.objectContaining({
|
|
246
|
+
mode: 'nameonly',
|
|
247
|
+
name: mockError.name,
|
|
248
|
+
message: mockError.message,
|
|
249
|
+
stackString: `${mockError.name}: ${mockError.message}`,
|
|
250
|
+
frames: []
|
|
251
|
+
}));
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// describe('')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
var stripNewlinesRegex = /^\n+|\n+$/g;
|
|
7
|
+
var MAX_STACK_TRACE_LENGTH = 65530;
|
|
8
|
+
export function formatStackTrace(stackLines) {
|
|
9
|
+
return truncateStackLines(stackLines).replace(stripNewlinesRegex, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// takes array of stack lines and returns string with top 50 and buttom 50 lines
|
|
13
|
+
function truncateStackLines(stackLines) {
|
|
14
|
+
var stackString;
|
|
15
|
+
if (stackLines.length > 100) {
|
|
16
|
+
var truncatedLines = stackLines.length - 100;
|
|
17
|
+
stackString = stackLines.slice(0, 50).join('\n');
|
|
18
|
+
stackString += '\n< ...truncated ' + truncatedLines + ' lines... >\n';
|
|
19
|
+
stackString += stackLines.slice(-50).join('\n');
|
|
20
|
+
} else {
|
|
21
|
+
stackString = stackLines.join('\n');
|
|
22
|
+
}
|
|
23
|
+
return stackString;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// truncates stack string to limit what is sent to backend
|
|
27
|
+
export function truncateSize(stackString) {
|
|
28
|
+
return stackString.length > MAX_STACK_TRACE_LENGTH ? stackString.substr(0, MAX_STACK_TRACE_LENGTH) : stackString;
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import { formatStackTrace, truncateSize } from './format-stack-trace';
|
|
3
|
+
describe('formatStackTrace', () => {
|
|
4
|
+
test.each([{
|
|
5
|
+
input: ['line 1', 'line 2', 'line 3', 'line 4'],
|
|
6
|
+
expected: 'line 1\nline 2\nline 3\nline 4',
|
|
7
|
+
title: 'Appends all stack lines together'
|
|
8
|
+
}, {
|
|
9
|
+
input: ['', 'line 1', 'line 2'],
|
|
10
|
+
expected: 'line 1\nline 2',
|
|
11
|
+
title: 'Strips leading empty stack frame'
|
|
12
|
+
}, {
|
|
13
|
+
input: ['line 1', 'line 2', ''],
|
|
14
|
+
expected: 'line 1\nline 2',
|
|
15
|
+
title: 'Strips ending empty stack frame'
|
|
16
|
+
}])('$title', ({
|
|
17
|
+
input,
|
|
18
|
+
expected
|
|
19
|
+
}) => {
|
|
20
|
+
const result = formatStackTrace(input);
|
|
21
|
+
expect(result).toEqual(expected);
|
|
22
|
+
});
|
|
23
|
+
test('truncates the middle of the stack lines when more than 100', () => {
|
|
24
|
+
const input = Array.apply(null, Array(200)).map(() => faker.datatype.uuid());
|
|
25
|
+
const expected = input.slice(0, 50).join('\n') + `\n< ...truncated ${input.length - 100} lines... >\n` + input.slice(-50).join('\n');
|
|
26
|
+
const result = formatStackTrace(input);
|
|
27
|
+
expect(result).toEqual(expected);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('truncateSize', () => {
|
|
31
|
+
test('should truncate stack string to max limit', () => {
|
|
32
|
+
const maxSize = 65530;
|
|
33
|
+
const input = faker.datatype.string(maxSize + 1);
|
|
34
|
+
const result = truncateSize(input);
|
|
35
|
+
expect(result).toEqual(input.slice(0, maxSize));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { canonicalFunctionName } from './canonical-function-name';
|
|
7
|
+
import { cleanURL } from '../../../common/url/clean-url';
|
|
8
|
+
import { computeStackTrace } from './compute-stack-trace';
|
|
9
|
+
import { stringHashCode } from './string-hash-code';
|
|
10
|
+
import { truncateSize } from './format-stack-trace';
|
|
11
|
+
import { registerHandler as register } from '../../../common/event-emitter/register-handler';
|
|
12
|
+
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
13
|
+
import { stringify } from '../../../common/util/stringify';
|
|
14
|
+
import { handle } from '../../../common/event-emitter/handle';
|
|
15
|
+
import { mapOwn } from '../../../common/util/map-own';
|
|
16
|
+
import { getInfo, getConfigurationValue, getRuntime } from '../../../common/config/config';
|
|
17
|
+
import { now } from '../../../common/timing/now';
|
|
18
|
+
import { globalScope } from '../../../common/util/global-scope';
|
|
19
|
+
import { AggregateBase } from '../../utils/aggregate-base';
|
|
20
|
+
import { FEATURE_NAME } from '../constants';
|
|
21
|
+
import { drain } from '../../../common/drain/drain';
|
|
22
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
23
|
+
import { onWindowLoad } from '../../../common/window/load';
|
|
24
|
+
export class Aggregate extends AggregateBase {
|
|
25
|
+
static featureName = FEATURE_NAME;
|
|
26
|
+
constructor(agentIdentifier, aggregator) {
|
|
27
|
+
super(agentIdentifier, aggregator, FEATURE_NAME);
|
|
28
|
+
this.stackReported = {};
|
|
29
|
+
this.pageviewReported = {};
|
|
30
|
+
this.errorCache = {};
|
|
31
|
+
this.currentBody;
|
|
32
|
+
this.errorOnPage = false;
|
|
33
|
+
|
|
34
|
+
// this will need to change to match whatever ee we use in the instrument
|
|
35
|
+
this.ee.on('interactionSaved', interaction => this.onInteractionSaved(interaction));
|
|
36
|
+
|
|
37
|
+
// this will need to change to match whatever ee we use in the instrument
|
|
38
|
+
this.ee.on('interactionDiscarded', interaction => this.onInteractionDiscarded(interaction));
|
|
39
|
+
register('err', (...args) => this.storeError(...args), this.featureName, this.ee);
|
|
40
|
+
register('ierr', (...args) => this.storeError(...args), this.featureName, this.ee);
|
|
41
|
+
const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'jserrors.harvestTimeSeconds') || 10;
|
|
42
|
+
const scheduler = new HarvestScheduler('jserrors', {
|
|
43
|
+
onFinished: (...args) => this.onHarvestFinished(...args)
|
|
44
|
+
}, this);
|
|
45
|
+
scheduler.harvest.on('jserrors', (...args) => this.onHarvestStarted(...args));
|
|
46
|
+
|
|
47
|
+
// Don't start harvesting until "drain" for this feat has been called (which currently requires RUM response).
|
|
48
|
+
this.ee.on(`drain-${this.featureName}`, () => {
|
|
49
|
+
if (!this.blocked) scheduler.startTimer(harvestTimeSeconds); // and only if ingest will accept jserror payloads
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// If RUM-call's response determines that customer lacks entitlements for the /jserror ingest endpoint, don't harvest at all.
|
|
53
|
+
register('block-err', () => {
|
|
54
|
+
this.blocked = true;
|
|
55
|
+
scheduler.stopTimer(true);
|
|
56
|
+
}, this.featureName, this.ee);
|
|
57
|
+
drain(this.agentIdentifier, this.featureName);
|
|
58
|
+
}
|
|
59
|
+
onHarvestStarted(options) {
|
|
60
|
+
// this gets rid of dependency in AJAX module
|
|
61
|
+
var body = this.aggregator.take(['err', 'ierr', 'xhr']);
|
|
62
|
+
if (options.retry) {
|
|
63
|
+
this.currentBody = body;
|
|
64
|
+
}
|
|
65
|
+
var payload = {
|
|
66
|
+
body: body,
|
|
67
|
+
qs: {}
|
|
68
|
+
};
|
|
69
|
+
var releaseIds = stringify(getRuntime(this.agentIdentifier).releaseIds);
|
|
70
|
+
if (releaseIds !== '{}') {
|
|
71
|
+
payload.qs.ri = releaseIds;
|
|
72
|
+
}
|
|
73
|
+
if (body && body.err && body.err.length && !this.errorOnPage) {
|
|
74
|
+
payload.qs.pve = '1';
|
|
75
|
+
this.errorOnPage = true;
|
|
76
|
+
}
|
|
77
|
+
return payload;
|
|
78
|
+
}
|
|
79
|
+
onHarvestFinished(result) {
|
|
80
|
+
if (result.retry && this.currentBody) {
|
|
81
|
+
mapOwn(this.currentBody, (key, value) => {
|
|
82
|
+
for (var i = 0; i < value.length; i++) {
|
|
83
|
+
var bucket = value[i];
|
|
84
|
+
var name = this.getBucketName(bucket.params, bucket.custom);
|
|
85
|
+
this.aggregator.merge(key, name, bucket.metrics, bucket.params, bucket.custom);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
this.currentBody = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
nameHash(params) {
|
|
92
|
+
return stringHashCode(`${params.exceptionClass}_${params.message}_${params.stack_trace || params.browser_stack_hash}`);
|
|
93
|
+
}
|
|
94
|
+
getBucketName(params, customParams) {
|
|
95
|
+
return this.nameHash(params) + ':' + stringHashCode(stringify(customParams));
|
|
96
|
+
}
|
|
97
|
+
canonicalizeURL(url, cleanedOrigin) {
|
|
98
|
+
if (typeof url !== 'string') return '';
|
|
99
|
+
var cleanedURL = cleanURL(url);
|
|
100
|
+
if (cleanedURL === cleanedOrigin) {
|
|
101
|
+
return '<inline>';
|
|
102
|
+
} else {
|
|
103
|
+
return cleanedURL;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
buildCanonicalStackString(stackInfo, cleanedOrigin) {
|
|
107
|
+
var canonicalStack = '';
|
|
108
|
+
for (var i = 0; i < stackInfo.frames.length; i++) {
|
|
109
|
+
var frame = stackInfo.frames[i];
|
|
110
|
+
var func = canonicalFunctionName(frame.func);
|
|
111
|
+
if (canonicalStack) canonicalStack += '\n';
|
|
112
|
+
if (func) canonicalStack += func + '@';
|
|
113
|
+
if (typeof frame.url === 'string') canonicalStack += frame.url;
|
|
114
|
+
if (frame.line) canonicalStack += ':' + frame.line;
|
|
115
|
+
}
|
|
116
|
+
return canonicalStack;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Strip query parameters and fragments from the stackString property of the
|
|
120
|
+
// given stackInfo, along with the 'url' properties of each frame in
|
|
121
|
+
// stackInfo.frames.
|
|
122
|
+
//
|
|
123
|
+
// Any URLs that are equivalent to the cleaned version of the origin will also
|
|
124
|
+
// be replaced with the string '<inline>'.
|
|
125
|
+
//
|
|
126
|
+
canonicalizeStackURLs(stackInfo) {
|
|
127
|
+
// Currently, loader.origin might contain a fragment, but we don't want to use it
|
|
128
|
+
// for comparing with frame URLs.
|
|
129
|
+
var cleanedOrigin = cleanURL(getRuntime(this.agentIdentifier).origin);
|
|
130
|
+
for (var i = 0; i < stackInfo.frames.length; i++) {
|
|
131
|
+
var frame = stackInfo.frames[i];
|
|
132
|
+
var originalURL = frame.url;
|
|
133
|
+
var cleanedURL = this.canonicalizeURL(originalURL, cleanedOrigin);
|
|
134
|
+
if (cleanedURL && cleanedURL !== frame.url) {
|
|
135
|
+
frame.url = cleanedURL;
|
|
136
|
+
stackInfo.stackString = stackInfo.stackString.split(originalURL).join(cleanedURL);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return stackInfo;
|
|
140
|
+
}
|
|
141
|
+
storeError(err, time, internal, customAttributes) {
|
|
142
|
+
// are we in an interaction
|
|
143
|
+
time = time || now();
|
|
144
|
+
if (!internal && getRuntime(this.agentIdentifier).onerror && getRuntime(this.agentIdentifier).onerror(err)) return;
|
|
145
|
+
var stackInfo = this.canonicalizeStackURLs(computeStackTrace(err));
|
|
146
|
+
var canonicalStack = this.buildCanonicalStackString(stackInfo);
|
|
147
|
+
var params = {
|
|
148
|
+
stackHash: stringHashCode(canonicalStack),
|
|
149
|
+
exceptionClass: stackInfo.name,
|
|
150
|
+
request_uri: globalScope?.location.pathname
|
|
151
|
+
};
|
|
152
|
+
if (stackInfo.message) {
|
|
153
|
+
params.message = '' + stackInfo.message;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
|
|
158
|
+
* stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
|
|
159
|
+
* the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
|
|
160
|
+
* bucketing and ultimately resulting in the loss of data in NR1.
|
|
161
|
+
*/
|
|
162
|
+
var bucketHash = stringHashCode(`${stackInfo.name}_${stackInfo.message}_${stackInfo.stackString}`);
|
|
163
|
+
if (!this.stackReported[bucketHash]) {
|
|
164
|
+
this.stackReported[bucketHash] = true;
|
|
165
|
+
params.stack_trace = truncateSize(stackInfo.stackString);
|
|
166
|
+
} else {
|
|
167
|
+
params.browser_stack_hash = stringHashCode(stackInfo.stackString);
|
|
168
|
+
}
|
|
169
|
+
params.releaseIds = stringify(getRuntime(this.agentIdentifier).releaseIds);
|
|
170
|
+
|
|
171
|
+
// When debugging stack canonicalization/hashing, uncomment these lines for
|
|
172
|
+
// more output in the test logs
|
|
173
|
+
// params.origStack = err.stack
|
|
174
|
+
// params.canonicalStack = canonicalStack
|
|
175
|
+
|
|
176
|
+
if (!this.pageviewReported[bucketHash]) {
|
|
177
|
+
params.pageview = 1;
|
|
178
|
+
this.pageviewReported[bucketHash] = true;
|
|
179
|
+
}
|
|
180
|
+
var type = internal ? 'ierr' : 'err';
|
|
181
|
+
var newMetrics = {
|
|
182
|
+
time: time
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// stn and spa aggregators listen to this event - stn sends the error in its payload,
|
|
186
|
+
// and spa annotates the error with interaction info
|
|
187
|
+
handle('errorAgg', [type, bucketHash, params, newMetrics], undefined, FEATURE_NAMES.sessionTrace, this.ee);
|
|
188
|
+
handle('errorAgg', [type, bucketHash, params, newMetrics], undefined, FEATURE_NAMES.spa, this.ee);
|
|
189
|
+
|
|
190
|
+
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
191
|
+
if (this.blocked) return;
|
|
192
|
+
if (params._interactionId != null) {
|
|
193
|
+
// hold on to the error until the interaction finishes
|
|
194
|
+
this.errorCache[params._interactionId] = this.errorCache[params._interactionId] || [];
|
|
195
|
+
this.errorCache[params._interactionId].push([type, bucketHash, params, newMetrics, att, customAttributes]);
|
|
196
|
+
} else {
|
|
197
|
+
// store custom attributes
|
|
198
|
+
var customParams = {};
|
|
199
|
+
var att = getInfo(this.agentIdentifier).jsAttributes;
|
|
200
|
+
mapOwn(att, setCustom);
|
|
201
|
+
if (customAttributes) {
|
|
202
|
+
mapOwn(customAttributes, setCustom);
|
|
203
|
+
}
|
|
204
|
+
var jsAttributesHash = stringHashCode(stringify(customParams));
|
|
205
|
+
var aggregateHash = bucketHash + ':' + jsAttributesHash;
|
|
206
|
+
this.aggregator.store(type, aggregateHash, params, newMetrics, customParams);
|
|
207
|
+
}
|
|
208
|
+
function setCustom(key, val) {
|
|
209
|
+
customParams[key] = val && typeof val === 'object' ? stringify(val) : val;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
onInteractionSaved(interaction) {
|
|
213
|
+
if (!this.errorCache[interaction.id] || this.blocked) return;
|
|
214
|
+
this.errorCache[interaction.id].forEach(item => {
|
|
215
|
+
var customParams = {};
|
|
216
|
+
var globalCustomParams = item[4];
|
|
217
|
+
var localCustomParams = item[5];
|
|
218
|
+
mapOwn(globalCustomParams, setCustom);
|
|
219
|
+
mapOwn(interaction.root.attrs.custom, setCustom);
|
|
220
|
+
mapOwn(localCustomParams, setCustom);
|
|
221
|
+
var params = item[2];
|
|
222
|
+
params.browserInteractionId = interaction.root.attrs.id;
|
|
223
|
+
delete params._interactionId;
|
|
224
|
+
if (params._interactionNodeId) {
|
|
225
|
+
params.parentNodeId = params._interactionNodeId.toString();
|
|
226
|
+
delete params._interactionNodeId;
|
|
227
|
+
}
|
|
228
|
+
var hash = item[1] + interaction.root.attrs.id;
|
|
229
|
+
var jsAttributesHash = stringHashCode(stringify(customParams));
|
|
230
|
+
var aggregateHash = hash + ':' + jsAttributesHash;
|
|
231
|
+
this.aggregator.store(item[0], aggregateHash, params, item[3], customParams);
|
|
232
|
+
function setCustom(key, val) {
|
|
233
|
+
customParams[key] = val && typeof val === 'object' ? stringify(val) : val;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
delete this.errorCache[interaction.id];
|
|
237
|
+
}
|
|
238
|
+
onInteractionDiscarded(interaction) {
|
|
239
|
+
if (!this.errorCache || !this.errorCache[interaction.id] || this.blocked) return;
|
|
240
|
+
this.errorCache[interaction.id].forEach(item => {
|
|
241
|
+
var customParams = {};
|
|
242
|
+
var globalCustomParams = item[4];
|
|
243
|
+
var localCustomParams = item[5];
|
|
244
|
+
mapOwn(globalCustomParams, setCustom);
|
|
245
|
+
mapOwn(interaction.root.attrs.custom, setCustom);
|
|
246
|
+
mapOwn(localCustomParams, setCustom);
|
|
247
|
+
var params = item[2];
|
|
248
|
+
delete params._interactionId;
|
|
249
|
+
delete params._interactionNodeId;
|
|
250
|
+
var hash = item[1];
|
|
251
|
+
var jsAttributesHash = stringHashCode(stringify(customParams));
|
|
252
|
+
var aggregateHash = hash + ':' + jsAttributesHash;
|
|
253
|
+
this.aggregator.store(item[0], aggregateHash, item[2], item[3], customParams);
|
|
254
|
+
function setCustom(key, val) {
|
|
255
|
+
customParams[key] = val && typeof val === 'object' ? stringify(val) : val;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
delete this.errorCache[interaction.id];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function stringHashCode(string) {
|
|
7
|
+
var hash = 0;
|
|
8
|
+
var charVal;
|
|
9
|
+
if (!string || !string.length) return hash;
|
|
10
|
+
for (var i = 0; i < string.length; i++) {
|
|
11
|
+
charVal = string.charCodeAt(i);
|
|
12
|
+
hash = (hash << 5) - hash + charVal;
|
|
13
|
+
hash = hash | 0; // Convert to 32bit integer
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return hash;
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { stringHashCode } from './string-hash-code';
|
|
2
|
+
test.each([{
|
|
3
|
+
input: undefined,
|
|
4
|
+
expected: 0,
|
|
5
|
+
title: 'Return 0 for undefined input'
|
|
6
|
+
}, {
|
|
7
|
+
input: null,
|
|
8
|
+
expected: 0,
|
|
9
|
+
title: 'Return 0 for null input'
|
|
10
|
+
}, {
|
|
11
|
+
input: '',
|
|
12
|
+
expected: 0,
|
|
13
|
+
title: 'Return 0 for empty string input'
|
|
14
|
+
}, {
|
|
15
|
+
input: 'lksjdflksjdf',
|
|
16
|
+
expected: 32668720,
|
|
17
|
+
title: 'Return valid hash of string'
|
|
18
|
+
}])('$title', ({
|
|
19
|
+
input,
|
|
20
|
+
expected
|
|
21
|
+
}) => {
|
|
22
|
+
const result = stringHashCode(input);
|
|
23
|
+
expect(result).toEqual(expected);
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Instrument as JSErrors } from './instrument/index';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ee } from '../../../common/event-emitter/contextual-ee';
|
|
7
|
+
import { mapOwn } from '../../../common/util/map-own';
|
|
8
|
+
var flags = {};
|
|
9
|
+
var flagArr;
|
|
10
|
+
try {
|
|
11
|
+
flagArr = localStorage.getItem('__nr_flags').split(',');
|
|
12
|
+
if (console && typeof console.log === 'function') {
|
|
13
|
+
flags.console = true;
|
|
14
|
+
if (flagArr.indexOf('dev') !== -1) flags.dev = true;
|
|
15
|
+
if (flagArr.indexOf('nr_dev') !== -1) flags.nrDev = true;
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {
|
|
18
|
+
// no op
|
|
19
|
+
}
|
|
20
|
+
if (flags.nrDev) ee.on('internal-error', function (err) {
|
|
21
|
+
log(err.stack);
|
|
22
|
+
});
|
|
23
|
+
if (flags.dev) ee.on('fn-err', function (args, origThis, err) {
|
|
24
|
+
log(err.stack);
|
|
25
|
+
});
|
|
26
|
+
if (flags.dev) {
|
|
27
|
+
log('NR AGENT IN DEVELOPMENT MODE');
|
|
28
|
+
log('flags: ' + mapOwn(flags, function (key, val) {
|
|
29
|
+
return key;
|
|
30
|
+
}).join(', '));
|
|
31
|
+
}
|
|
32
|
+
function log(message) {
|
|
33
|
+
try {
|
|
34
|
+
if (flags.console) log(message);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
// no op
|
|
37
|
+
}
|
|
38
|
+
}
|