Merge pull request #807 from Safelite/feature/SSR-1371

Feature/ssr 1371
This commit is contained in:
SujathaAnishetty 2024-08-01 16:36:10 -04:00 committed by GitHub
commit 71fa711843
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 213 additions and 7 deletions

View file

@ -39,6 +39,23 @@ export default {
}
}
};
// Add a global Javascript exception handler for logging exceptions, this handler will log when there is an uncaught exception
window.onerror = (msg, url, line, col, error) => {
// Log the windows error
// Note that col & error are new to the HTML 5 spec and may not be
// supported in every browser. It worked for me in Chrome.
global.$logger.logError(msg, {
url: url,
line: line,
column: col ?? "",
error: error ?? "",
});
// If you return true, then error alerts (like in older versions of
// Internet Explorer) will be suppressed.
var suppressErrorAlert = true;
return suppressErrorAlert;
};
</script>
<style lang="scss">

View file

@ -22,7 +22,8 @@ const applicationConfig = Object.freeze({
GOOGLE_CALENDAR: 'https://www.google.com/calendar/render?action=TEMPLATE',
YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60',
OUTLOOK_CALENDAR:
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent'
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent',
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging"
});
export default applicationConfig;

View file

@ -0,0 +1,11 @@
const axiosResponseInterceptorMessages = {
NETWORK_ERROR:
"A network error occurred. " +
"This could be a bad URL, a CORS issue, or a dropped internet connection. " +
"It is impossible for us to know.",
STATUS_CODE_ERROR: "Status Code Error",
NO_RESPONSE_ERROR: "The request was made but no response was received",
GENERIC_ERROR: "Error",
};
export default axiosResponseInterceptorMessages;

View file

@ -0,0 +1,8 @@
const loggingEndpointMethods = {
LOG_INFORMATION: "log-information",
LOG_WARNING: "log-warning",
LOG_ERROR: "log-error",
LOG_CRITICAL: "log-critical",
};
export default loggingEndpointMethods;

View file

@ -5,6 +5,50 @@ import { useMainStore } from '@/store';
import applicationConfig from '@/constants/application-config.js';
import { GaCategories, GaActions, GaLabels } from '@/constants/analytics';
import headerKeys from '@/constants/header-keys';
import axiosResponseInterceptorMessages from "@/constants/axios-response-interceptor-messages.js";
// Add a response interceptor for global axios error handing.
axios.interceptors.response.use(
(response) => response,
(error) => {
let rejectionError = "";
if (typeof error.response === "undefined") {
// The request was not made, could be a bad url, bad connection or a CORS error.
rejectionError = {
message:
"A network error occurred. " +
"This could be a CORS issue or a dropped internet connection. " +
"It is impossible for us to know.",
cause: error,
response: error,
message: axiosResponseInterceptorMessages.NETWORK_ERROR
};
} else if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
rejectionError = {
response: error.response,
message: axiosResponseInterceptorMessages.STATUS_CODE_ERROR,
};
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
rejectionError = {
response: error.request,
message: axiosResponseInterceptorMessages.NO_RESPONSE_ERROR,
};
} else {
// Something happened in setting up the request that triggered an Error
rejectionError = {
response: error.message,
message: axiosResponseInterceptorMessages.GENERIC_ERROR,
};
}
return Promise.reject(rejectionError);
}
);
export default {
callHttpClient({ method, endpoint, payload, logApiCall = true }) {
@ -37,13 +81,23 @@ export default {
return resolve(response);
},
(error) => {
window.console.error(error);
// implement if analytics service is down
if (endpoint.includes('analytics')) {
return resolve({ data: '' });
if (logApiCall) {
analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE,
GaActions.RESULT,
`${GaLabels.ERROR}_${endpoint}`,
true
);
}
if (error.response.status != "404") {
global.$logger.logError(
`${method}: ${endpoint}: ${error.message}`,
error.response
);
}
return reject(error.response);
}
);

View file

@ -7,6 +7,13 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js';
jest.mock('axios');
jest.mock('@/mixins/analytics-mixin');
global.$logger = {
logInformation: jest.fn(),
logWarning: jest.fn(),
logError: jest.fn(),
logCritical: jest.fn(),
};
/** @ignore */
function setupMocksForHttpClient({
endpoint = null,

93
src/helpers/logger.js Normal file
View file

@ -0,0 +1,93 @@
import applicationConfig from "@/constants/application-config.js";
import axios from "axios";
import { useMainStore } from "@/store";
import loggingEndpointMethods from "@/constants/logging-endpoint-methods";
import headerKeys from "@/constants/header-keys";
export class Logger {
logInformation(message, details) {
this.writeLogEntry(
loggingEndpointMethods.LOG_INFORMATION,
this.formatLogEntry(message, details)
);
}
logWarning(message, details) {
this.writeLogEntry(
loggingEndpointMethods.LOG_WARNING,
this.formatLogEntry(message, details)
);
}
logError(message, details) {
this.writeLogEntry(loggingEndpointMethods.LOG_ERROR, this.formatLogEntry(message, details));
}
logCritical(message, details) {
this.writeLogEntry(
loggingEndpointMethods.LOG_CRITICAL,
this.formatLogEntry(message, details)
);
}
formatLogEntry(message, details) {
return `Application: ${applicationConfig.APPLICATION_NAME}\n${message}\n${
details ? JSON.stringify(details, undefined, 2) : ""
}`;
}
writeLogEntry(endpoint, logEntry) {
const store = useMainStore();
return new Promise((resolve, reject) => {
// If running locally or in the Dev environment show the log entries in the console.
if (
applicationConfig.CURRENT_ENVIRONMENT === "Localhost" ||
applicationConfig.CURRENT_ENVIRONMENT === "Dev" ||
applicationConfig.CURRENT_ENVIRONMENT === "SysTest"
) {
switch (endpoint) {
case loggingEndpointMethods.LOG_INFORMATION:
console.info(logEntry);
break;
case loggingEndpointMethods.LOG_WARNING:
console.warn(logEntry);
break;
case loggingEndpointMethods.LOG_ERROR:
console.error(logEntry);
break;
case loggingEndpointMethods.LOG_CRITICAL:
console.error(logEntry);
break;
default:
console.log(logEntry);
}
}
const url =
applicationConfig.CONSUMER_CF_DISTRO + applicationConfig.FRONTEND_LOGGER_PATH;
const headers = {
[headerKeys.EXPERIMENT]: JSON.stringify(store.experimentSettings),
};
axios({
method: "POST",
url: `${url}/${endpoint}`,
data: { entry: logEntry },
crossDomain: true,
responseType: {},
headers: headers,
}).then(
(response) => {
return resolve(response);
},
(error) => {
return reject(error);
}
);
});
}
}
export default Logger;

View file

@ -13,6 +13,10 @@ import defineGlobalRules from '@/helpers/global-rule-definer';
import router from './router';
import App from './App.vue';
import Logger from "@/helpers/logger";
// Instantiate global logging object
global.$logger = new Logger();
// Vue App Setup
const vueApp = createApp(App);
@ -35,6 +39,17 @@ vueApp.mixin(baseMixin);
vueApp.mixin(analyticsMixin);
vueApp.mixin(experimentMixin);
// Vue Error Handling
vueApp.config.errorHandler = (err, vm, info) => {
global.$logger.logError(
`Page Name - ${vm.getPageName()} - ${info}: ${err.message}\n${err.stack}`
);
};
// Vue Router Error Handling
router.onError((err) => {
global.$logger.logError(err.message, err.cause);
});
vueApp.mount('#app');
// define global rules