Preliminary practice of front-end error monitoring SDK

Preliminary practice of front-end error monitoring SDK

Preface

As a front-end, I often encounter situations where online errors are reported but cannot be reproduced. If there is error monitoring at this time, it can quickly help us locate the problem, and then find the relevant information of the error monitoring to try to implement an own SDK. Here I and two terms as a focus point to be realized. Complete code

Error monitoring

Runtime error

  window.onerror = (msg,url,lineNo,columnNo,e) => {
   //... 
  }
 

promise reject was not processed

  window.addEventListener('unhandledrejection',(event) => {
   // 
    const { message,config:{method,url} } = event.reason
   //... 
  })
 

Rewrite the native listener event


 //  addEventListener  
  const originAddEventListener = EventTarget.prototype.addEventListener
 // 
  EventTarget.prototype.addEventListener = (type,listener,options) => {
    const wrappedListenner = (...args) => {
      try {
        return listener.apply(this,args)
      } catch (error) {
        const { name,message } = error
       //... 
        throw error
      }
    }
    return originAddEventListener.call(this,type,wrappedListenner,options)
  }
 

Hijack Vue.config.errorHandler

  const vueErrorHandler = Vue.config.errorHandler
  const wrapErrorHandler = function(err,vm,info) {
    const componentRouteInfo = vm.$route
   // 
    const { fullPath,name } = componentRouteInfo
   //... 
    vueErrorHandler.call(this,err,vm,info)
  }
  _Vue.config.errorHandler = wrapErrorHandler
 

Obtaining equipment information

Page scrolling information

 // 
  let scrollPosition  = []
 //  throttle  
  const scrollLog = throttle(() => {
   //... , 
    scrollPosition = []
  }, scrollTime)
  function scrollHandler(e) {
   //  window  
   //  e.taget.scrollTop  
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop 
   // 
   // 
    scrollPosition.push(scrollTop.toFixed(0))
   //... 
    scrollLog(e)
  }
  const throttleScrollHandler = throttle(scrollHandler)
  window.addEventListener('scroll',throttleScrollHandler)
 

Click event

  function clickHandler(e:any) {
    const { target } = e
    const clickInfo = judgeDomType(target)
    logGif(clickInfo)
  }
  window.addEventListener('click',clickHandler)
 // :  
  function judgeDomType(target:HTMLElement) {
    const { nodeType, nodeName, nodeValue,className,id} = target
    let firstChild
    switch (nodeType) {
     // 
      case Node.ELEMENT_NODE:
        firstChild = searchBottomNestChild(target)
        break
    }
    return {
      firstChild,
      selector: `class-${className};id-${id}`,
      nodeName
    }
  }
 // 
  function searchBottomNestChild(dom:any) {
    let current = dom
    while (current.firstChild) {
      current = current.firstChild
    }
    return current.nodeValue
  }
 

Equipment related information

const pageLog = () => {
  const userAgent = navigator.userAgent
  let webview = ''
  if (userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
    webview = 'ios'
  }
  if (userAgent.match(/MicroMessenger\/([^\s]+)/)) {
    webview = 'weixin'
  }
  if (userAgent.match(/QQ\/([\d\.]+)/)) {
    webview = 'qq'
  }
 // 
  const connection = navigator.connection
  return {
    logType:LogType.page,
    path:location.href,
    platform:navigator.platform,
    webview,
    connection
  }
}
 

send data

  • The element created by the new Image method only needs to assign the src attribute to send the request without inserting it into the document.
  • It should be noted that when splicing parameters, you need to use encodeURIComponent to transfer the value. Otherwise, it will cause an error to use a URL such as location.href as a value.
function parseJsonToString(dataJson) {
  if (!dataJson ) { dataJson = {} }
  var dataArr = Object.keys(dataJson).map(function(key) { return key + '=' + encodeURIComponent(dataJson[key]) })
  return dataArr.join('&')
}

const logGif = (params) => {
  const upload = parseJsonToString(params)
  const img = new Image(1,1)
  img.src = 'https://view-error?' + upload
}
 

Package the sdk file

After completing the basic error monitoring function, we can package these files into a js file, and use it directly through script introduction when other projects need to be applied.

The configuration of webpack.config.js will be much less than in our project. Since I am using typescript + vue, the configuration is as follows:

const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)
module.exports = {
  entry:{
   //sdk 
    'view-error':resolve('../src/utils/index.ts')
  },
  output:{
    path:resolve('../dist')
  },
  resolve: {
    extensions:['.ts','.js']
  },
  module: {
    rules:[
     //  .ts  
      {
        test:/\.tsx?$/,
        loader: 'ts-loader'
      }
    ]
  }
}
 

Server-side processing

Connect to the database

const mysql = require('mysql')
const pool = mysql.createPool({
  host: 'ip',
  user: 'admin',
  password: 'password',
  database: 'view_error'
})
//  sql  , 
function connect(sql) {
  return new Promise((resolve, reject) => {
    pool.getConnection((err, connection) => {
      if (err) reject(err)
      console.log('   ')
      connection.query(sql, function(error, results, fields) {
        if (error) reject(error)
        resolve(results)
        connection.release()
      })
    })
  })
}
 

Insert data

exports.insert = async function (ctx) {
  const query = JSON.stringify(ctx.request.query)
 // , 
  await connect(`insert into view_error (root) values('${query}')`)
 // 
  const file = fs.readFileSync('upload/icon-image.gif')
  ctx.type = 'image/gif'
  ctx.body = file
}
 

Questions and optimization

problem

  1. Currently, the page jump is monitored through vue-router. I tried it window.addEventListener('popstate',()=>{})and window.addEventListener('hashchange',()=>{})listens for changes in less than a page vue route. Can js natively monitor routing changes?
  2. Want to record the operation link of a single user, how to associate the user link, through the association with ip,
  3. How to categorize storage queries when storing on the server side is more convenient and quick, and the server side is not very familiar.

To be optimized

  1. When the front-end sends data, the format is unified, and the request is divided into several categories or corresponding levels.
  2. Optimize the information collected by the client and the capture of some error messages.
  3. The same information on the server is classified and stored to optimize storage space.