diff --git a/CHANGELOG.md b/CHANGELOG.md index 80797e14..94ba33d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## X.X.X - Added a new flag, 'loadAPMScriptsAsync', which can load the APM related scripts automatically for Async implementations +- Adding SDK health check requests after init - Adding remaining request queue size information to every request - Fixed a bug where unnecessary device ID merge information was sent to the server diff --git a/cypress/integration/health_check.js b/cypress/integration/health_check.js new file mode 100644 index 00000000..bb570df8 --- /dev/null +++ b/cypress/integration/health_check.js @@ -0,0 +1,37 @@ +/* eslint-disable require-jsdoc */ +var Countly = require("../../lib/countly"); +var hp = require("../support/helper"); + +function initMain() { + Countly.init({ + app_key: "YOUR_APP_KEY", + url: "https://try.count.ly", + test_mode: true + }); +} + +describe("Health Check tests ", () => { + it("Check if health check is sent at the beginning", () => { + hp.haltAndClearStorage(() => { + initMain(); + cy.intercept("GET", "https://try.count.ly/i?**").as("getXhr"); + cy.wait("@getXhr").then((xhr) => { + const url = new URL(xhr.request.url); + + // Test the 'hc' parameter + const hcParam = url.searchParams.get("hc"); + const hcParamObj = JSON.parse(hcParam); + expect(hcParamObj).to.eql({ el: 0, wl: 0, sc: -1, em: "\"\"" }); + + // Test the 'metrics' parameter + const metricsParam = url.searchParams.get("metrics"); + expect(metricsParam).to.equal("{\"_app_version\":\"0.0\",\"_ua\":\"abcd\"}"); + + // check nothing in the request queue + cy.fetch_local_request_queue().then((rq) => { + expect(rq.length).to.equal(0); + }); + }); + }); + }); +}); diff --git a/lib/countly.js b/lib/countly.js index ee05723f..fe6cad83 100644 --- a/lib/countly.js +++ b/lib/countly.js @@ -164,6 +164,16 @@ BOOMERANG_SRC: "https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/boomerang.min.js", CLY_BOOMERANG_SRC: "https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/countly_boomerang.js", }; + + /** + * Health check counters' local storage keys + */ + var healthCheckCounterEnum = Object.freeze({ + errorCount: "cly_hc_error_count", + warningCount: "cly_hc_warning_count", + statusCode: "cly_hc_status_code", + errorMessage: "cly_hc_error_message", + }); /** * Async api queue, push commands here to be executed when script is loaded or after * @example Add command as array @@ -295,6 +305,10 @@ this.maxStackTraceLinesPerThread = getConfig("max_stack_trace_lines_per_thread", ob, configurationDefaultValues.MAX_STACKTRACE_LINES_PER_THREAD); this.maxStackTraceLineLength = getConfig("max_stack_trace_line_length", ob, configurationDefaultValues.MAX_STACKTRACE_LINE_LENGTH); this.heatmapWhitelist = getConfig("heatmap_whitelist", ob, []); + self.hcErrorCount = getValueFromStorage(healthCheckCounterEnum.errorCount) || 0; + self.hcWarningCount = getValueFromStorage(healthCheckCounterEnum.warningCount) || 0; + self.hcStatusCode = getValueFromStorage(healthCheckCounterEnum.statusCode) || -1; + self.hcErrorMessage = getValueFromStorage(healthCheckCounterEnum.errorMessage) || ""; if (maxCrashLogs && !this.maxBreadcrumbCount) { this.maxBreadcrumbCount = maxCrashLogs; @@ -3756,6 +3770,13 @@ if (last - lastBeat > sessionUpdate) { self.session_duration(last - lastBeat); lastBeat = last; + // save health check logging counters if there are any + if (self.hcErrorCount > 0) { + setValueInStorage(healthCheckCounterEnum.errorCount, self.hcErrorCount); + } + if (self.hcWarningCount > 0) { + setValueInStorage(healthCheckCounterEnum.warningCount, self.hcWarningCount); + } } } @@ -3941,10 +3962,12 @@ if (level === logLevelEnums.ERROR) { // eslint-disable-next-line no-console console.error(log); + HealthCheck.incrementErrorCount(); } else if (level === logLevelEnums.WARNING) { // eslint-disable-next-line no-console console.warn(log); + HealthCheck.incrementWarningCount(); } else if (level === logLevelEnums.INFO) { // eslint-disable-next-line no-console @@ -4019,8 +4042,11 @@ } else { log(logLevelEnums.ERROR, functionName + " Failed Server XML HTTP request, ", this.status); + if (functionName === "send_request_queue") { + HealthCheck.saveRequestCounters(this.status, this.responseText); + } if (typeof callback === "function") { - callback(true, params); + callback(true, params, this.status, this.responseText); } } } @@ -4491,7 +4517,101 @@ } }; + /** + * Health Check Interface: + * {sendInstantHCRequest} Sends instant health check request + * {resetAndSaveCounters} Resets and saves health check counters + * {incrementErrorCount} Increments health check error count + * {incrementWarningCount} Increments health check warning count + * {resetCounters} Resets health check counters + * {saveRequestCounters} Saves health check request counters + */ + var HealthCheck = {}; + HealthCheck.sendInstantHCRequest = sendInstantHCRequest; + HealthCheck.resetAndSaveCounters = resetAndSaveCounters; + HealthCheck.incrementErrorCount = incrementErrorCount; + HealthCheck.incrementWarningCount = incrementWarningCount; + HealthCheck.resetCounters = resetCounters; + HealthCheck.saveRequestCounters = saveRequestCounters; + /** + * Increments health check error count + */ + function incrementErrorCount() { + self.hcErrorCount++; + } + /** + * Increments health check warning count + */ + function incrementWarningCount() { + self.hcWarningCount++; + } + /** + * Resets health check counters + */ + function resetCounters() { + self.hcErrorCount = 0; + self.hcWarningCount = 0; + self.hcStatusCode = -1; + self.hcErrorMessage = ""; + } + /** + * Sets and saves the status code and error message counters + * @param {number} status - response status code of the request + * @param {string} responseText - response text of the request + */ + function saveRequestCounters(status, responseText) { + self.hcStatusCode = status; + self.hcErrorMessage = responseText; + setValueInStorage(healthCheckCounterEnum.statusCode, self.hcStatusCode); + setValueInStorage(healthCheckCounterEnum.errorMessage, self.hcErrorMessage); + } + /** + * Resets and saves health check counters + */ + function resetAndSaveCounters() { + HealthCheck.resetCounters(); + setValueInStorage(healthCheckCounterEnum.errorCount, self.hcErrorCount); + setValueInStorage(healthCheckCounterEnum.warningCount, self.hcWarningCount); + setValueInStorage(healthCheckCounterEnum.statusCode, self.hcStatusCode); + setValueInStorage(healthCheckCounterEnum.errorMessage, self.hcErrorMessage); + } + /** + * Countly health check request sender + */ + function sendInstantHCRequest() { + // truncate error message to 1000 characters + var curbedMessage = truncateSingleValue(self.hcErrorMessage, 1000, "healthCheck", log); + // prepare hc object + var hc = { + el: self.hcErrorCount, + wl: self.hcWarningCount, + sc: self.hcStatusCode, + em: JSON.stringify(curbedMessage) + }; + // prepare request + var request = { + hc: JSON.stringify(hc), + metrics: JSON.stringify({ _app_version: self.app_version }) + }; + // add common request params + prepareRequest(request); + // send request + sendXmlHttpRequest("healthCheck", self.url + apiPath, request, function(err, params, status, responseText) { + if (err) { + log(logLevelEnums.ERROR, "healthCheck, An error occurred. Status: [" + status + "], response: [" + responseText + "]"); + } + else { + log(logLevelEnums.INFO, "healthCheck, Request was successful. Status: [" + status + "], response: [" + responseText + "]"); + // reset and save health check counters if request was successful + HealthCheck.resetAndSaveCounters(); + } + }, true); + } + + // initialize Countly Class this.initialize(); + // send instant health check request + HealthCheck.sendInstantHCRequest(); }; /**