Skip to content

Commit 69aea55

Browse files
MoumitaMsaikumarrs
andauthored
Crash reporting metrics implementation with Bugsnag (#520)
* working on error reporting metrics * working on bugsnag integration * Update metrics/error-report/index.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update metrics/error-report/index.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update metrics/error-report/index.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * working on Bugsnag integration * bugsnag module: releaseStage updated to production * working on bugsnag * Update metrics/error-report/index.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update utils/utils.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * bugsnag integration * bugsnag integration * fix: load Bugsnag by default * fix: immediately initialize Bugsnag client * fix: remove unnecessary return statements * fix: bugsnag API key converted into a token to be replaced in CI * SDK meta data updated and comment added * updated bs configuration at initialisation * Update analytics.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * refactor: in-place text substitution in buildspec files * misc updates in bugsnag integration * removed event type from error message and removed version from sdk meta data * code cleaning * feat(ci): sourcemap files copied to S3 * Update analytics.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update analytics.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update analytics.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update analytics.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Update utils/utils.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * refactor: reimported get method from utils * replaced notifyError method with handleError * Bugsnag enhancements * Update metrics/error-report/Bugsnag.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * Bugsnag refactoring * code refactoring * fix(errors): refactored handleError method to block unwanted errors * Added 'Element.prototype.dataset' polyfill * Added 'loader' data attribute to script loader * chore(update): Added script data attribute in all integrations * fix: discard script errors not originating from SDK * updated ad-blocker script check condition * Discard all non-script loading errors * Removed error message filter * Removed unused variable * Fixed syntax issue * fix: module type update * fix: create new error object for script errors * fix: setting event context * fix: used replacer function in JSON stringify * updated default initialisation behavior of bugsnag * updated code comment * bugsnag config format modified * Update analytics.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * code refactoring * Update utils/constants.js Co-authored-by: Sai Kumar Battinoju <[email protected]> * code refactoring * code refactoring Co-authored-by: Sai Kumar Battinoju <[email protected]> Co-authored-by: saikumarrs <[email protected]>
1 parent df683e8 commit 69aea55

File tree

37 files changed

+328
-97
lines changed

37 files changed

+328
-97
lines changed

analytics.js

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getJSONTrimmed,
2020
generateUUID,
2121
handleError,
22+
leaveBreadcrumb,
2223
getDefaultPageProperties,
2324
getUserProvidedConfigUrl,
2425
findAllEnabledDestinations,
@@ -28,12 +29,15 @@ import {
2829
getReferrer,
2930
getReferringDomain,
3031
commonNames,
32+
get,
3133
} from "./utils/utils";
3234
import {
3335
CONFIG_URL,
3436
MAX_WAIT_FOR_INTEGRATION_LOAD,
3537
INTEGRATION_LOAD_CHECK_INTERVAL,
3638
POLYFILL_URL,
39+
DEFAULT_ERROR_REPORT_PROVIDER,
40+
ERROR_REPORT_PROVIDERS,
3741
} from "./utils/constants";
3842
import { integrations } from "./integrations";
3943
import RudderElementBuilder from "./utils/RudderElementBuilder";
@@ -44,6 +48,7 @@ import { addDomEventHandlers } from "./utils/autotrack.js";
4448
import ScriptLoader from "./integrations/ScriptLoader";
4549
import parseLinker from "./utils/linker";
4650
import CookieConsentFactory from "./cookieConsent/CookieConsentFactory";
51+
import * as BugsnagLib from "./metrics/error-report/Bugsnag";
4752

4853
const queryDefaults = {
4954
trait: "ajs_trait_",
@@ -167,6 +172,31 @@ class Analytics {
167172
if (typeof response === "string") {
168173
response = JSON.parse(response);
169174
}
175+
176+
// Fetch Error reporting enable option from sourceConfig
177+
const isErrorReportEnabled = get(
178+
response.source.config,
179+
"statsCollection.errorReports.enabled"
180+
);
181+
182+
// Load Bugsnag only if it is enabled in the source config
183+
if (isErrorReportEnabled === true) {
184+
// Fetch the name of the Error reporter from sourceConfig
185+
const provider = get(
186+
response.source.config,
187+
"statsCollection.errorReports.provider"
188+
) || DEFAULT_ERROR_REPORT_PROVIDER;
189+
if (!ERROR_REPORT_PROVIDERS.includes(provider)) {
190+
logger.error("Invalid error reporting provider value");
191+
}
192+
193+
if (provider === "bugsnag") {
194+
// Load Bugsnag client SDK
195+
BugsnagLib.load();
196+
BugsnagLib.init(response.source.id);
197+
}
198+
}
199+
170200
if (
171201
response.source.useAutoTracking &&
172202
!this.autoTrackHandlersRegistered
@@ -194,15 +224,15 @@ class Analytics {
194224
this.clientIntegrations
195225
);
196226

197-
var cookieConsent;
227+
let cookieConsent;
198228
// Call the cookie consent factory to initialize and return the type of cookie
199229
// consent being set. For now we only support OneTrust.
200230
try {
201231
cookieConsent = CookieConsentFactory.initialize(
202232
this.cookieConsentOptions
203233
);
204234
} catch (e) {
205-
logger.error(e);
235+
handleError(e);
206236
}
207237

208238
// If cookie consent object is return we filter according to consents given by user
@@ -214,7 +244,7 @@ class Analytics {
214244
(cookieConsent && cookieConsent.isEnabled(intg.config)))
215245
);
216246
});
217-
247+
leaveBreadcrumb("Starting device-mode initialization");
218248
this.init(this.clientIntegrations);
219249
} catch (error) {
220250
handleError(error);
@@ -254,21 +284,17 @@ class Analytics {
254284
let intgInstance;
255285
intgArray.forEach((intg) => {
256286
try {
257-
logger.debug(
258-
"[Analytics] init :: trying to initialize integration name:: ",
259-
intg.name
260-
);
287+
const msg = `[Analytics] init :: trying to initialize integration name:: ${intg.name}`;
288+
logger.debug(msg);
289+
leaveBreadcrumb(msg);
261290
const intgClass = integrations[intg.name];
262291
const destConfig = intg.config;
263292
intgInstance = new intgClass(destConfig, self);
264293
intgInstance.init();
265294
logger.debug("initializing destination: ", intg);
266295
this.isInitialized(intgInstance).then(this.replayEvents);
267296
} catch (e) {
268-
logger.error(
269-
"[Analytics] initialize integration (integration.init()) failed :: ",
270-
intg.name
271-
);
297+
handleError(e);
272298
this.failedToBeLoadedIntegration.push(intgInstance);
273299
}
274300
});
@@ -289,6 +315,7 @@ class Analytics {
289315
" failed loaded count: ",
290316
object.failedToBeLoadedIntegration.length
291317
);
318+
leaveBreadcrumb(`Started replaying buffered events`);
292319
// eslint-disable-next-line no-param-reassign
293320
object.clientIntegrationObjects = [];
294321
// eslint-disable-next-line no-param-reassign
@@ -412,6 +439,7 @@ class Analytics {
412439
* @memberof Analytics
413440
*/
414441
page(category, name, properties, options, callback) {
442+
leaveBreadcrumb(`Page event`);
415443
if (!this.loaded) return;
416444
if (typeof options === "function") (callback = options), (options = null);
417445
if (typeof properties === "function")
@@ -444,6 +472,7 @@ class Analytics {
444472
* @memberof Analytics
445473
*/
446474
track(event, properties, options, callback) {
475+
leaveBreadcrumb(`Track event`);
447476
if (!this.loaded) return;
448477
if (typeof options === "function") (callback = options), (options = null);
449478
if (typeof properties === "function")
@@ -462,6 +491,7 @@ class Analytics {
462491
* @memberof Analytics
463492
*/
464493
identify(userId, traits, options, callback) {
494+
leaveBreadcrumb(`Identify event`);
465495
if (!this.loaded) return;
466496
if (typeof options === "function") (callback = options), (options = null);
467497
if (typeof traits === "function")
@@ -480,6 +510,7 @@ class Analytics {
480510
* @param {*} callback
481511
*/
482512
alias(to, from, options, callback) {
513+
leaveBreadcrumb(`Alias event`);
483514
if (!this.loaded) return;
484515
if (typeof options === "function") (callback = options), (options = null);
485516
if (typeof from === "function")
@@ -507,6 +538,7 @@ class Analytics {
507538
* @param {*} callback
508539
*/
509540
group(groupId, traits, options, callback) {
541+
leaveBreadcrumb(`Group event`);
510542
if (!this.loaded) return;
511543
if (!arguments.length) return;
512544

@@ -705,27 +737,27 @@ class Analytics {
705737
// Blacklist is choosen for filtering events
706738
case "blacklistedEvents":
707739
if (Array.isArray(blacklistedEvents)) {
708-
return blacklistedEvents.find(
709-
(eventObj) =>
710-
eventObj.eventName.trim().toUpperCase() === formattedEventName
711-
) === undefined
712-
? false
713-
: true;
714-
} else {
715-
return false;
740+
return (
741+
blacklistedEvents.find(
742+
(eventObj) =>
743+
eventObj.eventName.trim().toUpperCase() === formattedEventName
744+
) !== undefined
745+
);
716746
}
747+
return false;
748+
717749
// Whitelist is choosen for filtering events
718750
case "whitelistedEvents":
719751
if (Array.isArray(whitelistedEvents)) {
720-
return whitelistedEvents.find(
721-
(eventObj) =>
722-
eventObj.eventName.trim().toUpperCase() === formattedEventName
723-
) === undefined
724-
? true
725-
: false;
726-
} else {
727-
return true;
752+
return (
753+
whitelistedEvents.find(
754+
(eventObj) =>
755+
eventObj.eventName.trim().toUpperCase() === formattedEventName
756+
) === undefined
757+
);
728758
}
759+
return true;
760+
729761
default:
730762
return false;
731763
}
@@ -747,7 +779,7 @@ class Analytics {
747779

748780
// assign page properties to context
749781
// rudderElement.message.context.page = getDefaultPageProperties();
750-
782+
leaveBreadcrumb("Started sending data to destinations");
751783
rudderElement.message.context.traits = {
752784
...this.userTraits,
753785
};
@@ -792,11 +824,12 @@ class Analytics {
792824
);
793825

794826
// try to first send to all integrations, if list populated from BE
795-
try {
796-
succesfulLoadedIntersectClientSuppliedIntegrations.forEach((obj) => {
827+
828+
succesfulLoadedIntersectClientSuppliedIntegrations.forEach((obj) => {
829+
try {
797830
if (!obj.isFailed || !obj.isFailed()) {
798831
if (obj[type]) {
799-
let sendEvent = !this.IsEventBlackListed(
832+
const sendEvent = !this.IsEventBlackListed(
800833
rudderElement.message.event,
801834
obj.name
802835
);
@@ -808,10 +841,11 @@ class Analytics {
808841
}
809842
}
810843
}
811-
});
812-
} catch (err) {
813-
handleError({ message: `[sendToNative]:${err}` });
814-
}
844+
} catch (err) {
845+
err.message = `[sendToNative]:: [Destination: ${obj.name}]:: "${err.message}"`;
846+
handleError(err);
847+
}
848+
});
815849

816850
// config plane native enabled destinations, still not completely loaded
817851
// in the page, add the events to a queue and process later
@@ -927,6 +961,8 @@ class Analytics {
927961
* @memberof Analytics
928962
*/
929963
reset(flag) {
964+
leaveBreadcrumb(`reset API :: flag: ${flag}`);
965+
930966
if (!this.loaded) return;
931967
if (flag) {
932968
this.anonymousId = "";
@@ -1012,11 +1048,9 @@ class Analytics {
10121048
this.cookieConsentOptions = cloneDeep(options.cookieConsentManager);
10131049
let configUrl = CONFIG_URL;
10141050
if (!this.isValidWriteKey(writeKey) || !this.isValidServerUrl(serverUrl)) {
1015-
handleError({
1016-
message:
1017-
"[Analytics] load:: Unable to load due to wrong writeKey or serverUrl",
1018-
});
1019-
throw Error("failed to initialize");
1051+
throw Error(
1052+
"Unable to load the SDK due to invalid writeKey or serverUrl"
1053+
);
10201054
}
10211055

10221056
let storageOptions = {};
@@ -1104,7 +1138,7 @@ class Analytics {
11041138
}
11051139
if (options && options.getSourceConfig) {
11061140
if (typeof options.getSourceConfig !== "function") {
1107-
handleError('option "getSourceConfig" must be a function');
1141+
handleError(new Error('option "getSourceConfig" must be a function'));
11081142
} else {
11091143
const res = options.getSourceConfig();
11101144

buildspec.prod.yaml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ phases:
66
nodejs: 12
77
build:
88
commands:
9-
- ls
109
- npm install --unsafe-perm
1110
- npm run prodTest && npm test
1211
- npm run buildProdBrowser
13-
- sed 's|//# sourceMappingURL=rudder-analytics.min.js.map||' dist/rudder-analytics.min.js > dist/prod.js
14-
- mv dist/prod.js dist/rudder-analytics.min.js
15-
- aws s3 cp dist/rudder-analytics.min.js s3://$S3_BUCKET_NAME/rudder-analytics.min.js --cache-control max-age=3600 --acl public-read
12+
- sed -i -e 's|{{RS_BUGSNAG_API_KEY}}|'$RS_BUGSNAG_API_KEY'|' dist/rudder-analytics.min.js
1613
- aws s3 cp dist/rudder-analytics.min.js s3://$S3_BUCKET_NAME/v1/rudder-analytics.min.js --cache-control max-age=3600 --acl public-read
17-
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1/rudder-analytics.min.js"
18-
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/rudder-analytics.min.js"
14+
- aws s3 cp dist/rudder-analytics.min.js.map s3://$S3_BUCKET_NAME/v1/rudder-analytics.min.js.map --cache-control max-age=3600 --acl public-read
15+
- aws s3 cp s3://$S3_BUCKET_NAME/v1/rudder-analytics.min.js s3://$S3_BUCKET_NAME/rudder-analytics.min.js --cache-control max-age=3600 --acl public-read
16+
- aws s3 cp s3://$S3_BUCKET_NAME/v1/rudder-analytics.min.js.map s3://$S3_BUCKET_NAME/rudder-analytics.min.js.map --cache-control max-age=3600 --acl public-read
17+
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1/rudder-analytics.min.js" "/v1/rudder-analytics.min.js.map" "/rudder-analytics.min.js" "/rudder-analytics.min.js.map"
1918
artifacts:
2019
files:
2120
- "**/*"

buildspec.staging.yaml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
version: 0.2
22

3-
43
phases:
54
install:
65
runtime-versions:
76
nodejs: 12
87
build:
98
commands:
10-
- ls
119
- npm install --unsafe-perm
1210
- npm run prodTest && npm test
1311
- npm run buildProdBrowser
14-
- sed 's|//# sourceMappingURL=rudder-analytics.min.js.map||' dist/rudder-analytics.min.js > dist/prod.js
15-
- mv dist/prod.js dist/rudder-analytics.min.js
16-
- aws s3 cp dist/rudder-analytics.min.js s3://$S3_BUCKET_NAME/rudder-analytics-staging.min.js --cache-control max-age=3600 --acl public-read
12+
- sed -i -e 's|rudder-analytics.min.js.map|rudder-analytics-staging.min.js.map|' -e 's|{{RS_BUGSNAG_API_KEY}}|'$RS_BUGSNAG_API_KEY'|' dist/rudder-analytics.min.js
1713
- aws s3 cp dist/rudder-analytics.min.js s3://$S3_BUCKET_NAME/v1/rudder-analytics-staging.min.js --cache-control max-age=3600 --acl public-read
18-
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1/rudder-analytics-staging.min.js"
19-
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/rudder-analytics-staging.min.js"
14+
- aws s3 cp dist/rudder-analytics.min.js.map s3://$S3_BUCKET_NAME/v1/rudder-analytics-staging.min.js.map --cache-control max-age=3600 --acl public-read
15+
- aws s3 cp s3://$S3_BUCKET_NAME/v1/rudder-analytics-staging.min.js s3://$S3_BUCKET_NAME/rudder-analytics-staging.min.js --cache-control max-age=3600 --acl public-read
16+
- aws s3 cp s3://$S3_BUCKET_NAME/v1/rudder-analytics-staging.min.js.map s3://$S3_BUCKET_NAME/rudder-analytics-staging.min.js.map --cache-control max-age=3600 --acl public-read
17+
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/v1/rudder-analytics-staging.min.js" "/v1/rudder-analytics-staging.min.js.map" "/rudder-analytics-staging.min.js" "/rudder-analytics-staging.min.js.map"
2018
artifacts:
2119
files:
2220
- "**/*"

integrations/Amplitude/browser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable class-methods-use-this */
33
import logger from "../../utils/logUtil";
44
import { type } from "../../utils/utils";
5+
import { LOAD_ORIGIN } from "../ScriptLoader";
56
import { NAME } from "./constants";
67

78
class Amplitude {
@@ -63,6 +64,7 @@ class Amplitude {
6364
"sha384-girahbTbYZ9tT03PWWj0mEVgyxtZoyDF9KVZdL+R53PP5wCY0PiVUKq0jeRlMx9M";
6465
r.crossOrigin = "anonymous";
6566
r.async = true;
67+
r.dataset.loader = LOAD_ORIGIN;
6668
r.src = "https://cdn.amplitude.com/libs/amplitude-7.2.1-min.gz.js";
6769
r.onload = function () {
6870
if (!e.amplitude.runQueuedFunctions) {

integrations/BingAds/browser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logger from "../../utils/logUtil";
2+
import { LOAD_ORIGIN } from "../ScriptLoader";
23
import { NAME } from "./constants";
34

45
class BingAds {
@@ -20,6 +21,7 @@ class BingAds {
2021
(n = d.createElement(t)),
2122
(n.src = r),
2223
(n.async = 1),
24+
(n.dataset.loader = LOAD_ORIGIN),
2325
(n.onload = n.onreadystatechange =
2426
function () {
2527
let s = this.readyState;

integrations/Braze/browser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable class-methods-use-this */
22
import logger from "../../utils/logUtil";
3+
import { LOAD_ORIGIN } from "../ScriptLoader";
34
import { NAME } from "./constants";
45

56
/*
@@ -88,6 +89,7 @@ class Braze {
8889
};
8990
(y = p.createElement(P)).type = "text/javascript";
9091
y.src = "https://js.appboycdn.com/web-sdk/2.4/appboy.min.js";
92+
y.dataset.loader = LOAD_ORIGIN;
9193
y.async = 1;
9294
(b = p.getElementsByTagName(P)[0]).parentNode.insertBefore(y, b);
9395
})(window, document, "script");

integrations/Chartbeat/browser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
INTEGRATION_LOAD_CHECK_INTERVAL,
77
} from "../../utils/constants";
88
import { NAME } from "./constants";
9+
import { LOAD_ORIGIN } from "../ScriptLoader";
910

1011
class Chartbeat {
1112
constructor(config, analytics) {
@@ -108,6 +109,7 @@ class Chartbeat {
108109
e.type = "text/javascript";
109110
e.async = true;
110111
e.src = `//static.chartbeat.com/js/${script}`;
112+
e.dataset.loader = LOAD_ORIGIN;
111113
n.parentNode.insertBefore(e, n);
112114
}
113115
loadChartbeat();

0 commit comments

Comments
 (0)