This document describes how to obtain detailed error information when errors or exceptions occur after the DEPLOYMENT of the Vue2 project and automatically notify relevant developers to handle them. Project Address, let’s get started!

Pain points

Usually when we are developing locally, Vue2 will give us a stack error message in the browser if an error or exception occurs

You can also see which component and which file the error occurred in which line

Clicking on the file opens the file in Console Sources and locates the error location

This greatly facilitates the process of finding the cause of the error, but when our code is deployed to the server, the sourcemap file is usually not packaged for project optimization, and the error message usually looks like this

Is that a blank look? Clicking on the corresponding file is also the packaged and compressed code, so there is no way to see the error message and corresponding to the specific file of the source code and the specific error location.

Not only that, when you receive business feedback, but you open the browser and do not encounter problems, do you feel at a loss to start?

The next step is to collect error information when the online environment reports an error and notify the developers in a timely manner.

Exceptions are typically caught in a browser using the following methods

addEventListener('error', callback)||window.onerror= callback
Copy the code

I won’t repeat it here.

Vue2 provides us with the API errorHandler for exception catching.

Note: my background service is created with node.js eggJS framework, you can use any of their familiar backend language and framework, the logic is very simple.

Main function points:

1. Generate the SourcemAP file and upload it to the background

Because Vue2 uses Webpack as a module packer, we need to write a Webpack plug-in, which is used to read all sourcemap files and upload them to the background after packaging, and delete the sourcemAP files in the input directory of packaging to reduce resource requests in the online environment

Introduced and used in vue.config.js

/ / into the upload sourcemap webpack plugin const UploadSourceMapWebPackPlugin = require('./src/plugins/uploadSourceMapWebPackPlugin') module.exports = { configureWebpack: { plugins: [/ / using the upload sourcemap webpack plugin new UploadSourceMapWebPackPlugin ({ emptyFolderUrl:`${process.env.VUE_APP_MONITOR_BASE_API}/mointor/emptyFolder`, uploadUrl: `${process.env.VUE_APP_MONITOR_BASE_API}/mointor/uploadSourceMap` }) ] } }Copy the code

uploadSourceMapWebPackPlugin.js

Const glob = require('glob') const path = require('path') const fs = require('fs') const HTTP = require('http') class UploadSourceMapWebPackPlugin { constructor(options) { this.options = options } apply(compiler) { Let env = ''; If (process.env.vue_app_base_api === 'test environment address ') {env = 'uat'} else if (process.env.vue_app_base_api ===' production environment address ') {env } / / define = 'prod' executive compiler. After packaging hooks. Done. Tap (' uploadSourceMapWebPackPlugin, async status = > {/ / processing target folder (not create, There are empty) HTTP get (` ${this. The options. EmptyFolderUrl}? env=${env}`,()=>{ console.log('handle folder success') }).on("error",(e)=>{ console.log(`handle folder error: ${e.m essage} `)}) / / read const sourcemap file list = glob. Sync (path. Join (status.com pilation. OutputOptions. Path, {js.map,}')) for (const filename of list) {// Upload sourcemap await this.upload(this.options. UploadUrl, filename, Env) // delete sourcemap await fs.unlinksync (filename)}})} file,env) { return new Promise(resolve => { const req = http.request( `${url}?name=${path.basename(file)}&&env=${env}`, { method: "POST", headers: { 'Content-Type': 'application/octet-stream', Connection: 'keep-alive', 'Transfer-Encoding': 'chunked'}}) // read the file and send it to the request object fs.createreadstream (file).on('data', chunk => {req.write(chunk)}).on('end', () => { req.end(); resolve(); }) }) }} module.exports = UploadSourceMapWebPackPlugin;Copy the code

The background emptyFolder

Async emptyFolder() {const {CTX} = this; async emptyFolder() {const {CTX} = this; const env = ctx.query.env; const dir = path.join(this.config.baseDir, `upload/${env}`); Upload /env if (! Fs.existssync (dir)) {fs.mkdirsync (dir) was not created; } else {// Clear const files = fs.readdirsync (dir); Files. forEach(file => {// const currentPath = dir + '/' + file; Fs.unlinksync (currentPath); fs.unlinksync (currentPath); }); }}Copy the code

2. The background service receives and saves the uploaded Sourcemap file

Async uploadSourceMap() {const {CTX} = this; const stream = ctx.req, filename = ctx.query.name, env = ctx.query.env; // Const dir = path.join(this.config.baseDir, 'upload/${env}'); // Const target = path.join(dir, filename); Const writeStream = fs.createWritestream (target); stream.pipe(writeStream); }Copy the code

3. Use errorHandler to catch exceptions and send error information to the background

Introduced and used in main.js

Import {handleError} from './utils/monitor'Copy the code

mointor.js

Import axios from 'axios / / browser information function getBrowserInfo () {const agent. = the navigator userAgent. ToLowerCase (); const regIE = /msie [\d.]+; /gi; const regIE11 = /rv:[\d.]+/gi; const regFireFox = /firefox/[\d.]+/gi; const regQQ = /qqbrowser/[\d.]+/gi; const regEdg = /edg/[\d.]+/gi; const regSafari = /safari/[\d.]+/gi; const regChrome = /chrome/[\d.]+/gi; If (regie.test (agent)) {return agent. Match (regIE)[0]; } // IE11 if (regIE11.test(agent)) { return 'IE11'; } // firefox if (regFireFox.test(agent)) { return agent.match(regFireFox)[0]; } // QQ if (regQQ.test(agent)) { return agent.match(regQQ)[0]; } // Edg if (regEdg.test(agent)) { return agent.match(regEdg)[0]; } // Chrome if (regChrome.test(agent)) { return agent.match(regChrome)[0]; } // Safari if (regSafari.test(agent)) { return agent.match(regSafari)[0]; }} const handleError = Vue => {// Vue catch error hook function Vue. Config. ErrorHandler = (err, Vm) => {// If (process.env.node_env === "development") throw Error(err) // Let environment = 'Test environment '; If (process.env.vue_app_base_api === "production environment address ") {environment = 'production environment'} /* Error message sent here you can customize any information, such as user information, user click history, user route history, etc */ axios({ method: 'post', url: `${process.env.VUE_APP_MONITOR_BASE_API}/mointor/reportError`, data: {environment, location: window.location.href, message: err.message, stack: err.stack, // Current component: Vm.$vnode.tag, // browser information browserInfo: GetBrowserInfo (), // userId userId:'001', // userName userName:' zhang SAN ', RouterHistory :[{fullPath:'/login', name:' login', query:{}, params:{},},{fullPath:'/home', name:' home', Query :{}, params:{},}], // Click clickHistory:[{pageX:50, pageY:50, nodeName:'div', className:'test', id:'test', InnerText :' Test button '}],}}); }} export { handleError }Copy the code

4. The background receives an error message, parses the error message with the Sourcemap file, and notifies the developer of the error message

Async reportError() {const {CTX} = this; async reportError() {const {CTX} = this; const { environment, location, message, stack, component, browserInfo, userId, userName, routerHistory, clickHistory } = ctx.request.body; let env = ''; If (environment === 'env ') {env = 'uat'; } else if (environment === 'env ') {env = 'prod'; } const sourceMapDir = path.join(this.config.baseDir, 'upload/${env}'); Const stackParser = new stackapparser (sourceMapDir); let routerHistoryStr = '<h3>router history</h3>', clickHistoryStr = '<h3>click history</h3>'; ForEach (item => {routerHistoryStr += `<p>name:${item.name} | fullPath:${item.fullPath}</p>`; routerHistoryStr += `<p>params:${JSON.stringify(item.params)} | query:${JSON.stringify(item.query)}</p><p>--------------------</p>`; }); Clickhistory.length &&clickHistory.foreach (item => {clickHistoryStr += `<p>pageX:${item.pageX} | pageY:${item.pageY}</p>`; clickHistoryStr += `<p>nodeName:${item.nodeName} | className:${item.className} | id:${item.id}</p>`; clickHistoryStr += `<p>innerText:${item.innerText}</p><p>--------------------</p>`; }); / / by giving sourcemap file, cooperate with the error information, parse error message const errInfo = await stackParser. ParseStackTrack (stack, message); // Get the current time const now = new Date(); const time = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; Const mailMsg = '<h3>message:${message}</h3> <h3>location:${location}</h3> <p> Component :${component}</p> <p>source:${errInfo.source}</p> <p>line::${errInfo.lineNumber}</p> <p>column:${errInfo.columnNumber}</p> <p>fileName:${errInfo.fileName}</p> <p>functionName:${errInfo.functionName}</p> <p>time::${time}</p> <p>browserInfo::${browserInfo}</p> <p>userId::${userId}</p> <p>userName::${userName}</p> ${routerHistoryStr} ${clickHistoryStr} `; // sendMail(' outbox address ', 'outbox authorization code ',' inbox address ', subject environment, body mailMsg); SendMail (' outbox address ', 'outbox authorization code ',' inbox address ', environment, mailMsg); ctx.body = { header: { code: 0, message: 'OK', }, }; ctx.status = 200; }Copy the code

sendMail

'use strict'; const nodemailer = require('nodemailer'); Function sendMail(from, fromPass, receivers, subject, msg) { const smtpTransport = nodemailer.createTransport({ host: 'smtp.qq.email', service: 'qq', secureConnection: true, // use SSL secure: true, port: 465, auth: { user: from, pass: fromPass, }, }); Smtptransport. sendMail({from, // Receivers, multiple mailboxes are separated by a comma between them. To: Receivers, // Mail subject, // Mail body HTML: msg, }, err => { if (err) { console.log('send mail error: ', err); }}); } module.exports = sendMail;Copy the code

End

Vue3 uses rollup as a module packer, so there will be some differences when writing package plugins.

If you have any questions or suggestions about this article, please leave a comment!

\