import { isUnitTest } from '@/utils/unit.js'
import Vue from 'vue'
import axios from 'axios'
import api from '@/api'
import * as Sentry from '@sentry/browser'
import { Integrations } from '@sentry/tracing'
import { default as createMitt } from 'mitt'
import envService from '@/services/env-service.js'
import { getQueryStringValue } from '@/utils/querystring'
import APIUrls from '@/config/simpliciti-apis.js'
//Disabled due to bad performance from APIV3 and auth errors
const IS_APIV3_ERROR_LOGGING_ENABLED = false
const shouldLog = (getQueryStringValue('verbose') || '').includes('1')
const mitt = createMitt()
export const errorLogger = createSimplicitiErrorLogger()
Vue.use(SimplicitiLoggerPlugin())
Vue.use(SentryLoggerPlugin())
/**
* This plugin will automatically catch all errors. and send them to server (APIV3)
*
*
* If the error states 'Script Error', take a look at:
* https://scotch.io/bar-talk/what-the-heck-is-script-error
*
* @deprecated server logging disabled due to Sentry being enough (and some APIV3 issues)
* @example
*
* Usage (From outside components)
*
* Vue.ErrorLogger.logError(error)
*
* Usage (From inside components)
*
* this.$logError(error)
*
* Usage (Listen errors)
* Vue.ErrorLogger.$on("error")
*
*/
function SimplicitiLoggerPlugin() {
return {
install: function () {
Vue.ErrorLogger = errorLogger
Vue.prototype.$logError = (message, metadata) =>
errorLogger.logError(message, metadata)
Vue.prototype.$logError._sentry = Sentry
axios.interceptors.response.use(
function (response) {
return response
},
function (error) {
error.stack = `${error.request.status} (${error.request.statusText}) at ${error.request.responseURL} : ${error.stack}`
return Promise.reject(error)
}
)
errorLogger.bindToBrowserErrors()
errorLogger.bindToVueErrors()
},
}
}
function SentryLoggerPlugin() {
return {
install() {
let isLocalhost = window.location.origin.indexOf('localhost') !== -1
if (
!isLocalhost &&
!!process.env.VUE_APP_SENTRY_DSN &&
process.env.VUE_APP_SENTRY_ENABLED === '1'
) {
Sentry.init({
Vue,
dsn: envService.getEnvValue('VUE_APP_SENTRY_DSN'),
autoSessionTracking: true,
integrations: [new Integrations.BrowserTracing()],
logErrors: true,
tracesSampleRate: 0.05,
release:
envService.getEnvValue('VUE_APP_RELEASE_NAME') || 'development',
environment: envService.getEnvName(),
})
shouldLog && console.log('SENTRY_LOGGER_INIT_OK')
}
},
}
}
/**
* Custom error logger. Initially, we use to send errors to server (APIV3) but this was disabled in favor of Sentry.
*
* @returns
*/
function createSimplicitiErrorLogger() {
const self = {
$on(name, callback) {
mitt.on(name, callback)
},
$emit(name, payload) {
mitt.emit(name, payload)
},
$once(name, callback) {
let wasCalled = false
let handler = (payload) => {
if (wasCalled) {
return
} else {
wasCalled = true
}
mitt.off(handler)
callback(payload)
}
mitt.on(name, handler)
},
shouldLogErrorsToServer: IS_APIV3_ERROR_LOGGING_ENABLED,
bindToVueErrors() {
//Skip errors binding if unit test mode
if (isUnitTest()) {
return
}
Vue.config.errorHandler = (err) => {
self.logNonManuallyTriggeredError(err)
throw err
}
},
bindToBrowserErrors() {
window.addEventListener('error', (err) =>
self.logNonManuallyTriggeredError(err)
)
window.addEventListener('unhandledrejection', (err) =>
self.logNonManuallyTriggeredError(err)
)
},
logNonManuallyTriggeredError(error) {
self.logError(
error,
{},
{
wasManuallyTriggered: false,
}
)
},
logError(error, metadata = {}, options = {}) {
if (!error) return
metadata = typeof metadata !== 'object' ? {} : metadata
if (error instanceof ErrorEvent && error.error) {
error = error.error
}
if (typeof PromiseRejectionEvent !== 'undefined') {
if (error instanceof PromiseRejectionEvent) {
return logErrorInternal(
(error.reason && error.reason.message) ||
error.message ||
'PromiseRejectionEvent',
{
...metadata,
stack: (error.reason && error.reason.stack) || error.stack || '',
}
)
}
}
if (error instanceof Error) {
metadata.stack = error.stack
return logErrorInternal(error.message || 'Error', metadata)
}
metadata.stack = metadata.stack || new Error('GENERIC').stack
if (typeof error === 'string') {
return logErrorInternal(error || 'Error', metadata)
}
if (typeof error === 'number') {
return logErrorInternal('Error', {
...metadata,
status: error,
})
}
return logError('Error', {
...metadata,
})
},
}
/**
* Manually triggered errors are also send to Sentry.
*
* @param {String} message
* @param {Object} metadata
* @param {Object} options
* @returns
*/
async function logErrorInternal(message, metadata = {}, options = {}) {
//Use case: A new error is triggered while logging to server
if (
metadata &&
metadata.stack &&
metadata.stack.indexOf('Network Error') !== -1
) {
return //Avoid Overflow
}
//Send manually triggered errors to Sentry
if (options.wasManuallyTriggered !== false && !!options.errorObject) {
Sentry.captureException(options.errorObject)
}
if (self.shouldLogErrorsToServer) {
try {
api.v3.post(APIUrls.APIV3_JAVASCRIPT_ERRORS, {
message,
metadata,
})
} catch (err) {
console.error('ERROR', 'Unable to log error', message, metadata)
}
} else {
console.error('ERROR', message, metadata)
}
self.$emit('error', {
message,
metadata,
})
}
return self
}
Source