Merge pull request #807 from Safelite/feature/SSR-1371
Feature/ssr 1371
This commit is contained in:
commit
71fa711843
8 changed files with 213 additions and 7 deletions
17
src/App.vue
17
src/App.vue
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
11
src/constants/axios-response-interceptor-messages.js
Normal file
11
src/constants/axios-response-interceptor-messages.js
Normal 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;
|
||||
8
src/constants/logging-endpoint-methods.js
Normal file
8
src/constants/logging-endpoint-methods.js
Normal 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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
93
src/helpers/logger.js
Normal 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;
|
||||
15
src/main.js
15
src/main.js
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue