Frontend Logging

This commit is contained in:
Leah Schumann 2024-02-29 09:35:07 -05:00
parent 43df7efcde
commit 763956caef
7 changed files with 157 additions and 8 deletions

View file

@ -12,6 +12,7 @@
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
export default {
name: "app",
setup() {
@ -33,6 +34,24 @@ export default {
loadingModal,
},
};
// 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">

6
src/constants/logger.js Normal file
View file

@ -0,0 +1,6 @@
const logEntryTypes = {
information: "info",
warning: "warn",
error: "fail",
critical: "crit",
};

View file

@ -0,0 +1,8 @@
const loggingEndpoints = {
LOG_INFORMATION: '/analytics/api/v1/analytics/log-information',
LOG_WARNING: '/analytics/api/v1/analytics/log-warning',
LOG_ERROR: '/analytics/api/v1/analytics/log-error',
LOG_CRITICAL: '/analytics/api/v1/analytics/log-critical',
}
export default loggingEndpoints;

View file

@ -1,12 +1,52 @@
import axios from "axios";
import analyticsMixIn from "@/mixins/analytics-mixin.js";
import store from "@/store";
import router from "@/router";
import { applicationConfig } from "@/constants/application-config.js";
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
import { headerKeys } from "@/constants/header-keys";
// 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 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,
};
} 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 = {
message: "Status Code Error",
cause: error.response,
};
} 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 = {
message: "The request was made but no response was received",
cause: error.request,
};
} else {
// Something happened in setting up the request that triggered an Error
rejectionError = {
message: "Error",
cause: error.message,
};
}
return Promise.reject(rejectionError);
}
);
export default {
callHttpClient({
method,
@ -50,8 +90,6 @@ export default {
return resolve(response);
},
(error) => {
console.error(error);
if (logApiCall) {
analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE,
@ -61,12 +99,9 @@ export default {
);
}
// do not route to error logic when no wipers found or no promo found (404s)
if (error.response.status != "404") {
router.navigateError();
}
global.$logger.logError(`${method}: ${endpoint}`, error);
return reject(error.response);
return reject(error);
}
);
});

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

@ -0,0 +1,62 @@
import loggingEndpoints from "@/constants/logging-endpoints"
import { applicationConfig } from "@/constants/application-config.js";
import axios from "axios";
import store from "@/store";
import { headerKeys } from "@/constants/header-keys";
export class Logger {
logInformation(message, details) {
this.writeLogEntry(loggingEndpoints.LOG_INFORMATION, this.formatLogEntry(message, details));
}
logWarning(message, details) {
this.writeLogEntry(loggingEndpoints.LOG_WARNING, this.formatLogEntry(message, details));
}
logError(message, details) {
this.writeLogEntry(loggingEndpoints.LOG_ERROR, this.formatLogEntry(message, details));
}
logCritical(message, details) {
this.writeLogEntry(loggingEndpoints.LOG_CRITICAL, this.formatLogEntry(message, details));
}
formatLogEntry(message, details) {
return `${message}\n${details ? JSON.stringify(details, undefined, 2) : ""}`;
}
writeLogEntry(endpoint, logEntry) {
return new Promise((resolve, reject) => {
const url = applicationConfig.FRONTEND_LOGGER_URL;
const payload = Object.assign({}, payload);
const headers = {
[headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings),
};
axios({
method: "POST",
url: `${url}${endpoint}`,
data: logEntry,
crossDomain: true,
responseType: {},
headers: headers,
}).then(
(response) => {
return resolve(response);
},
(error) => {
return reject(error);
}
);
});
}
saveLogEntry(logEntry) {
console.log(logEntry);
}
}
export default Logger;

View file

@ -8,6 +8,10 @@ import baseMixin from "@/mixins/base-mixin.js";
import analyticsMixin from "@/mixins/analytics-mixin.js";
import experimentMixin from "@/mixins/experiment-mixin.js";
import "../node_modules/bootstrap/dist/js/bootstrap.js";
import Logger from "@/helpers/logger";
// Instantiate global logging object
global.$logger = new Logger();
// Vue App Setup
const vueApp = createApp(App);
@ -20,4 +24,17 @@ vueApp.mixin(baseMixin);
vueApp.mixin(analyticsMixin);
vueApp.mixin(experimentMixin);
// Vue Error Handling
vueApp.config.errorHandler = (err, vm, info) => {
global.$logger.logError(err.message, {
info: info,
vm: vm,
});
};
// Vue Router Error Handling
router.onError((err) => {
global.$logger.logError(err.message, err.cause);
});
vueApp.mount("#app");

View file

@ -154,6 +154,8 @@ const routes = [
} catch (error) {
console.log(error);
global.$logger.logError(error);
if (to.query?.fmgPage === funnelStartPageName) {
deleteFunnelCookie();
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);